From f7de466314e7b8cbbf672eed75f7ba10b7d93f58 Mon Sep 17 00:00:00 2001 From: Dmitrijs Goloscapovs Date: Thu, 19 Oct 2023 10:48:46 +0300 Subject: [PATCH] .......PS. [ZBX-23572] backported support of additional SSH options for ssh.run[] item as the 5th parameter --- configure.ac | 3 + include/common.h | 1 + m4/check_enum.m4 | 45 ++ m4/libssh.m4 | 7 + m4/libssh2.m4 | 7 + src/zabbix_server/poller/Makefile.am | 9 + src/zabbix_server/poller/checks_ssh.c | 601 +----------------- src/zabbix_server/poller/ssh2_run.c | 477 ++++++++++++++ src/zabbix_server/poller/ssh_run.c | 443 +++++++++++++ src/zabbix_server/poller/ssh_run.h | 37 ++ ui/include/defines.inc.php | 2 +- ui/tests/selenium/items/testFormItem.php | 2 +- .../selenium/items/testFormItemPrototype.php | 2 +- .../lld/testFormLowLevelDiscovery.php | 2 +- 14 files changed, 1040 insertions(+), 598 deletions(-) create mode 100644 m4/check_enum.m4 create mode 100644 src/zabbix_server/poller/ssh2_run.c create mode 100644 src/zabbix_server/poller/ssh_run.c create mode 100644 src/zabbix_server/poller/ssh_run.h diff --git a/configure.ac b/configure.ac index af07f60e453..ed12f0ca1b0 100644 --- a/configure.ac +++ b/configure.ac @@ -1538,6 +1538,9 @@ ZBXJS_LIBS="$ZBXJS_LIBS $ZLIB_LIBS $LIBPTHREAD_LIBS" AM_CONDITIONAL(HAVE_IPMI, [test "x$have_ipmi" = "xyes"]) AM_CONDITIONAL(HAVE_LIBXML2, test "x$have_libxml2" = "xyes") +AM_CONDITIONAL(HAVE_SSH, [test "x$have_ssh" = "xyes (libssh)"]) +AM_CONDITIONAL(HAVE_SSH2, [test "x$have_ssh" = "xyes (libssh2)"]) + dnl Check if Zabbix internal IPC services are used have_ipcservice="no" if test "x$have_ipmi" = "xyes"; then diff --git a/include/common.h b/include/common.h index bc51dd218de..aac858a4b47 100644 --- a/include/common.h +++ b/include/common.h @@ -1844,4 +1844,5 @@ typedef enum zbx_err_codes_t; void zbx_md5buf2str(const md5_byte_t *md5, char *str); +#define zbx_strscpy(x, y) zbx_strlcpy(x, y, sizeof(x)) #endif diff --git a/m4/check_enum.m4 b/m4/check_enum.m4 new file mode 100644 index 00000000000..957fdd2d38b --- /dev/null +++ b/m4/check_enum.m4 @@ -0,0 +1,45 @@ +# Zabbix +# Copyright (C) 2001-2023 Zabbix SIA +# +# 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 2 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. +# +# ENUM_CHECK(ENUM,INCLUDE) +# +# Checks if the specified enumerator (or macro) constant exists +# in a header and defines C macro with prefix HAVE_. +# + +AC_DEFUN([ENUM_CHECK], [ + AC_MSG_CHECKING([for defined $1]) + + AS_VAR_PUSHDEF([enum_var], [enum_var_have_$1]) + + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ + #include <$2> + ]], [[ + int e = $1; + ]])], + [AS_VAR_SET([enum_var], [yes])], + [AS_VAR_SET([enum_var], [no])]) + + AS_IF([test yes = AS_VAR_GET([enum_var])],[ + AC_DEFINE_UNQUOTED(AS_TR_CPP(HAVE_$1), 1, [Define to 1 if $1 definition is available]) + AC_MSG_RESULT(yes) + ], [ + AC_MSG_RESULT(no) + ]) + + AS_VAR_POPDEF([enum_var]) +])dnl diff --git a/m4/libssh.m4 b/m4/libssh.m4 index 657ec208b46..3abbc862e64 100644 --- a/m4/libssh.m4 +++ b/m4/libssh.m4 @@ -127,6 +127,13 @@ AS_HELP_STRING([--with-ssh@<:@=DIR@:>@],[use SSH package @<:@default=no@:>@, DIR if test "x$found_ssh" = "xyes"; then AC_DEFINE([HAVE_SSH], 1, [Define to 1 if you have the 'libssh' library (-lssh)]) AC_MSG_RESULT(yes) + + ENUM_CHECK([SSH_OPTIONS_KEY_EXCHANGE],[libssh/libssh.h]) + ENUM_CHECK([SSH_OPTIONS_HOSTKEYS],[libssh/libssh.h]) + ENUM_CHECK([SSH_OPTIONS_CIPHERS_C_S],[libssh/libssh.h]) + ENUM_CHECK([SSH_OPTIONS_CIPHERS_S_C],[libssh/libssh.h]) + ENUM_CHECK([SSH_OPTIONS_HMAC_C_S],[libssh/libssh.h]) + ENUM_CHECK([SSH_OPTIONS_HMAC_S_C],[libssh/libssh.h]) else AC_MSG_RESULT(no) SSH_CFLAGS="" diff --git a/m4/libssh2.m4 b/m4/libssh2.m4 index eebe9700265..9956479cc8f 100644 --- a/m4/libssh2.m4 +++ b/m4/libssh2.m4 @@ -114,6 +114,13 @@ AS_HELP_STRING([--with-ssh2@<:@=DIR@:>@],[use SSH2 package @<:@default=no@:>@, D if test "x$found_ssh2" = "xyes"; then AC_DEFINE([HAVE_SSH2], 1, [Define to 1 if you have the 'libssh2' library (-lssh2)]) AC_MSG_RESULT(yes) + + ENUM_CHECK([LIBSSH2_METHOD_KEX],[libssh2.h]) + ENUM_CHECK([LIBSSH2_METHOD_HOSTKEY],[libssh2.h]) + ENUM_CHECK([LIBSSH2_METHOD_CRYPT_CS],[libssh2.h]) + ENUM_CHECK([LIBSSH2_METHOD_CRYPT_SC],[libssh2.h]) + ENUM_CHECK([LIBSSH2_METHOD_MAC_CS],[libssh2.h]) + ENUM_CHECK([LIBSSH2_METHOD_MAC_SC],[libssh2.h]) else AC_MSG_RESULT(no) SSH2_CFLAGS="" diff --git a/src/zabbix_server/poller/Makefile.am b/src/zabbix_server/poller/Makefile.am index 2336063f9cf..0958573aa0b 100644 --- a/src/zabbix_server/poller/Makefile.am +++ b/src/zabbix_server/poller/Makefile.am @@ -27,11 +27,20 @@ libzbxpoller_a_SOURCES = \ checks_snmp.h \ checks_ssh.c \ checks_ssh.h \ + ssh_run.h \ checks_telnet.c \ checks_telnet.h \ poller.c \ poller.h +if HAVE_SSH +libzbxpoller_a_SOURCES += ssh_run.c +endif + +if HAVE_SSH2 +libzbxpoller_a_SOURCES += ssh2_run.c +endif + libzbxpoller_server_a_SOURCES = \ checks_internal.h \ checks_internal_server.c diff --git a/src/zabbix_server/poller/checks_ssh.c b/src/zabbix_server/poller/checks_ssh.c index e3fcafd5dcb..d48a100d8c1 100644 --- a/src/zabbix_server/poller/checks_ssh.c +++ b/src/zabbix_server/poller/checks_ssh.c @@ -19,604 +19,14 @@ #include "checks_ssh.h" -/* the size of temporary buffer used to read from data channel */ -#define DATA_BUFFER_SIZE 4096 - -#if defined(HAVE_SSH2) -#include -#elif defined (HAVE_SSH) -#include -#endif - #if defined(HAVE_SSH2) || defined(HAVE_SSH) -#include "comms.h" -#include "log.h" - -#define SSH_RUN_KEY "ssh.run" -#endif - -#if defined(HAVE_SSH2) -static const char *password; - -static void kbd_callback(const char *name, int name_len, const char *instruction, - int instruction_len, int num_prompts, - const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts, - LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses, void **abstract) -{ - (void)name; - (void)name_len; - (void)instruction; - (void)instruction_len; - - if (num_prompts == 1) - { - responses[0].text = zbx_strdup(NULL, password); - responses[0].length = strlen(password); - } - - (void)prompts; - (void)abstract; -} - -static int waitsocket(int socket_fd, LIBSSH2_SESSION *session) -{ - struct timeval tv; - int rc, dir; - fd_set fd, *writefd = NULL, *readfd = NULL; - - tv.tv_sec = 10; - tv.tv_usec = 0; - - FD_ZERO(&fd); - FD_SET(socket_fd, &fd); - - /* now make sure we wait in the correct direction */ - dir = libssh2_session_block_directions(session); - - if (0 != (dir & LIBSSH2_SESSION_BLOCK_INBOUND)) - readfd = &fd; - - if (0 != (dir & LIBSSH2_SESSION_BLOCK_OUTBOUND)) - writefd = &fd; - - rc = select(socket_fd + 1, readfd, writefd, NULL, &tv); - - return rc; -} - -/* example ssh.run["ls /"] */ -static int ssh_run(DC_ITEM *item, AGENT_RESULT *result, const char *encoding) -{ - zbx_socket_t s; - LIBSSH2_SESSION *session; - LIBSSH2_CHANNEL *channel; - int auth_pw = 0, rc, ret = NOTSUPPORTED, exitcode; - char tmp_buf[DATA_BUFFER_SIZE], *userauthlist, *publickey = NULL, *privatekey = NULL, *ssherr, - *output, *buffer = NULL; - size_t offset = 0, buf_size = DATA_BUFFER_SIZE; - - zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); - - if (FAIL == zbx_tcp_connect(&s, CONFIG_SOURCE_IP, item->interface.addr, item->interface.port, 0, - ZBX_TCP_SEC_UNENCRYPTED, NULL, NULL)) - { - SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot connect to SSH server: %s", zbx_socket_strerror())); - goto close; - } - - /* initializes an SSH session object */ - if (NULL == (session = libssh2_session_init())) - { - SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot initialize SSH session")); - goto tcp_close; - } - - /* set blocking mode on session */ - libssh2_session_set_blocking(session, 1); - - /* Create a session instance and start it up. This will trade welcome */ - /* banners, exchange keys, and setup crypto, compression, and MAC layers */ - if (0 != libssh2_session_startup(session, s.socket)) - { - libssh2_session_last_error(session, &ssherr, NULL, 0); - SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot establish SSH session: %s", ssherr)); - goto session_free; - } - - /* check what authentication methods are available */ - if (NULL != (userauthlist = libssh2_userauth_list(session, item->username, strlen(item->username)))) - { - if (NULL != strstr(userauthlist, "password")) - auth_pw |= 1; - if (NULL != strstr(userauthlist, "keyboard-interactive")) - auth_pw |= 2; - if (NULL != strstr(userauthlist, "publickey")) - auth_pw |= 4; - } - else - { - libssh2_session_last_error(session, &ssherr, NULL, 0); - SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot obtain authentication methods: %s", ssherr)); - goto session_close; - } - - zabbix_log(LOG_LEVEL_DEBUG, "%s() supported authentication methods:'%s'", __func__, userauthlist); - - switch (item->authtype) - { - case ITEM_AUTHTYPE_PASSWORD: - if (auth_pw & 1) - { - /* we could authenticate via password */ - if (0 != libssh2_userauth_password(session, item->username, item->password)) - { - libssh2_session_last_error(session, &ssherr, NULL, 0); - SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Password authentication failed: %s", - ssherr)); - goto session_close; - } - else - zabbix_log(LOG_LEVEL_DEBUG, "%s() password authentication succeeded", __func__); - } - else if (auth_pw & 2) - { - /* or via keyboard-interactive */ - password = item->password; - if (0 != libssh2_userauth_keyboard_interactive(session, item->username, &kbd_callback)) - { - libssh2_session_last_error(session, &ssherr, NULL, 0); - SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Keyboard-interactive authentication" - " failed: %s", ssherr)); - goto session_close; - } - else - zabbix_log(LOG_LEVEL_DEBUG, "%s() keyboard-interactive authentication succeeded", - __func__); - } - else - { - SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Unsupported authentication method." - " Supported methods: %s", userauthlist)); - goto session_close; - } - break; - case ITEM_AUTHTYPE_PUBLICKEY: - if (auth_pw & 4) - { - if (NULL == CONFIG_SSH_KEY_LOCATION) - { - SET_MSG_RESULT(result, zbx_strdup(NULL, "Authentication by public key failed." - " SSHKeyLocation option is not set")); - goto session_close; - } - - /* or by public key */ - publickey = zbx_dsprintf(publickey, "%s/%s", CONFIG_SSH_KEY_LOCATION, item->publickey); - privatekey = zbx_dsprintf(privatekey, "%s/%s", CONFIG_SSH_KEY_LOCATION, - item->privatekey); - - if (SUCCEED != zbx_is_regular_file(publickey)) - { - SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot access public key file %s", - publickey)); - goto session_close; - } - - if (SUCCEED != zbx_is_regular_file(privatekey)) - { - SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot access private key file %s", - privatekey)); - goto session_close; - } - - rc = libssh2_userauth_publickey_fromfile(session, item->username, publickey, - privatekey, item->password); - - if (0 != rc) - { - libssh2_session_last_error(session, &ssherr, NULL, 0); - SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Public key authentication failed:" - " %s", ssherr)); - goto session_close; - } - else - zabbix_log(LOG_LEVEL_DEBUG, "%s() authentication by public key succeeded", - __func__); - } - else - { - SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Unsupported authentication method." - " Supported methods: %s", userauthlist)); - goto session_close; - } - break; - } - - /* exec non-blocking on the remove host */ - while (NULL == (channel = libssh2_channel_open_session(session))) - { - switch (libssh2_session_last_error(session, NULL, NULL, 0)) - { - /* marked for non-blocking I/O but the call would block. */ - case LIBSSH2_ERROR_EAGAIN: - waitsocket(s.socket, session); - continue; - default: - SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot establish generic session channel")); - goto session_close; - } - } - - dos2unix(item->params); /* CR+LF (Windows) => LF (Unix) */ - /* request a shell on a channel and execute command */ - while (0 != (rc = libssh2_channel_exec(channel, item->params))) - { - switch (rc) - { - case LIBSSH2_ERROR_EAGAIN: - waitsocket(s.socket, session); - continue; - default: - SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot request a shell")); - goto channel_close; - } - } - - buffer = (char *)zbx_malloc(buffer, buf_size); - - while (0 != (rc = libssh2_channel_read(channel, tmp_buf, sizeof(tmp_buf)))) - { - if (rc < 0) - { - if (LIBSSH2_ERROR_EAGAIN == rc) - waitsocket(s.socket, session); - - SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot read data from SSH server")); - goto channel_close; - } - - if (MAX_EXECUTE_OUTPUT_LEN <= offset + rc) - { - SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Command output exceeded limit of %d KB", - MAX_EXECUTE_OUTPUT_LEN / ZBX_KIBIBYTE)); - goto channel_close; - } - - zbx_str_memcpy_alloc(&buffer, &buf_size, &offset, tmp_buf, rc); - } - - output = convert_to_utf8(buffer, offset, encoding); - zbx_rtrim(output, ZBX_WHITESPACE); - zbx_replace_invalid_utf8(output); - - SET_TEXT_RESULT(result, output); - output = NULL; - - ret = SYSINFO_RET_OK; -channel_close: - /* close an active data channel */ - exitcode = 127; - while (LIBSSH2_ERROR_EAGAIN == (rc = libssh2_channel_close(channel))) - waitsocket(s.socket, session); - - zbx_free(buffer); - - if (0 != rc) - { - libssh2_session_last_error(session, &ssherr, NULL, 0); - zabbix_log(LOG_LEVEL_WARNING, "%s() cannot close generic session channel: %s", __func__, ssherr); - } - else - exitcode = libssh2_channel_get_exit_status(channel); +#include "ssh_run.h" - zabbix_log(LOG_LEVEL_DEBUG, "%s() exitcode:%d bytecount:" ZBX_FS_SIZE_T, __func__, exitcode, offset); - - libssh2_channel_free(channel); - channel = NULL; - -session_close: - libssh2_session_disconnect(session, "Normal Shutdown"); - -session_free: - libssh2_session_free(session); - -tcp_close: - zbx_tcp_close(&s); - -close: - zbx_free(publickey); - zbx_free(privatekey); - zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret)); - - return ret; -} -#elif defined(HAVE_SSH) - -/* example ssh.run["ls /"] */ -static int ssh_run(DC_ITEM *item, AGENT_RESULT *result, const char *encoding) -{ - ssh_session session; - ssh_channel channel; - ssh_key privkey = NULL, pubkey = NULL; - int rc, userauth, ret = NOTSUPPORTED; - char *output, *publickey = NULL, *privatekey = NULL, *buffer = NULL; - char tmp_buf[DATA_BUFFER_SIZE], userauthlist[64]; - size_t offset = 0, buf_size = DATA_BUFFER_SIZE; - - zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); - - /* initializes an SSH session object */ - if (NULL == (session = ssh_new())) - { - SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot initialize SSH session")); - zabbix_log(LOG_LEVEL_DEBUG, "Cannot initialize SSH session"); - - goto close; - } - - /* set blocking mode on session */ - ssh_set_blocking(session, 1); - - /* create a session instance and start it up */ - if (0 != ssh_options_set(session, SSH_OPTIONS_HOST, item->interface.addr) || - 0 != ssh_options_set(session, SSH_OPTIONS_PORT, &item->interface.port) || - 0 != ssh_options_set(session, SSH_OPTIONS_USER, item->username)) - { - SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot set SSH session options: %s", - ssh_get_error(session))); - goto session_free; - } - - if (SSH_OK != ssh_connect(session)) - { - SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot establish SSH session: %s", ssh_get_error(session))); - goto session_free; - } - - /* check which authentication methods are available */ - if (SSH_AUTH_ERROR == ssh_userauth_none(session, NULL)) - { - SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Error during authentication: %s", ssh_get_error(session))); - goto session_close; - } - - userauthlist[0] = '\0'; - - if (0 != (userauth = ssh_userauth_list(session, NULL))) - { - if (0 != (userauth & SSH_AUTH_METHOD_NONE)) - offset += zbx_snprintf(userauthlist + offset, sizeof(userauthlist) - offset, "none, "); - if (0 != (userauth & SSH_AUTH_METHOD_PASSWORD)) - offset += zbx_snprintf(userauthlist + offset, sizeof(userauthlist) - offset, "password, "); - if (0 != (userauth & SSH_AUTH_METHOD_INTERACTIVE)) - offset += zbx_snprintf(userauthlist + offset, sizeof(userauthlist) - offset, - "keyboard-interactive, "); - if (0 != (userauth & SSH_AUTH_METHOD_PUBLICKEY)) - offset += zbx_snprintf(userauthlist + offset, sizeof(userauthlist) - offset, "publickey, "); - if (0 != (userauth & SSH_AUTH_METHOD_HOSTBASED)) - offset += zbx_snprintf(userauthlist + offset, sizeof(userauthlist) - offset, "hostbased, "); - if (2 <= offset) - userauthlist[offset-2] = '\0'; - } - - zabbix_log(LOG_LEVEL_DEBUG, "%s() supported authentication methods: %s", __func__, userauthlist); - - switch (item->authtype) - { - case ITEM_AUTHTYPE_PASSWORD: - if (0 != (userauth & SSH_AUTH_METHOD_PASSWORD)) - { - /* we could authenticate via password */ - if (SSH_AUTH_SUCCESS != ssh_userauth_password(session, NULL, item->password)) - { - SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Password authentication failed: %s", - ssh_get_error(session))); - goto session_close; - } - else - zabbix_log(LOG_LEVEL_DEBUG, "%s() password authentication succeeded", __func__); - } - else if (0 != (userauth & SSH_AUTH_METHOD_INTERACTIVE)) - { - /* or via keyboard-interactive */ - while (SSH_AUTH_INFO == (rc = ssh_userauth_kbdint(session, item->username, NULL))) - { - if (1 == ssh_userauth_kbdint_getnprompts(session) && - 0 != ssh_userauth_kbdint_setanswer(session, 0, item->password)) - { - zabbix_log(LOG_LEVEL_DEBUG,"Cannot set answer: %s", - ssh_get_error(session)); - } - } - - if (SSH_AUTH_SUCCESS != rc) - { - SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Keyboard-interactive authentication" - " failed: %s", ssh_get_error(session))); - goto session_close; - } - else - { - zabbix_log(LOG_LEVEL_DEBUG, "%s() keyboard-interactive authentication" - " succeeded", __func__); - } - } - else - { - SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Unsupported authentication method." - " Supported methods: %s", userauthlist)); - goto session_close; - } - break; - case ITEM_AUTHTYPE_PUBLICKEY: - if (0 != (userauth & SSH_AUTH_METHOD_PUBLICKEY)) - { - if (NULL == CONFIG_SSH_KEY_LOCATION) - { - SET_MSG_RESULT(result, zbx_strdup(NULL, "Authentication by public key failed." - " SSHKeyLocation option is not set")); - goto session_close; - } - - /* or by public key */ - publickey = zbx_dsprintf(publickey, "%s/%s", CONFIG_SSH_KEY_LOCATION, item->publickey); - privatekey = zbx_dsprintf(privatekey, "%s/%s", CONFIG_SSH_KEY_LOCATION, - item->privatekey); - - if (SUCCEED != zbx_is_regular_file(publickey)) - { - SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot access public key file %s", - publickey)); - goto session_close; - } - - if (SUCCEED != zbx_is_regular_file(privatekey)) - { - SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot access private key file %s", - privatekey)); - goto session_close; - } - - if (SSH_OK != ssh_pki_import_pubkey_file(publickey, &pubkey)) - { - SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Failed to import public key: %s", - ssh_get_error(session))); - goto session_close; - } - - if (SSH_AUTH_SUCCESS != ssh_userauth_try_publickey(session, NULL, pubkey)) - { - SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Public key try failed: %s", - ssh_get_error(session))); - goto session_close; - } - - if (SSH_OK != (rc = ssh_pki_import_privkey_file(privatekey, item->password, NULL, NULL, - &privkey))) - { - if (SSH_EOF == rc) - { - SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot import private key" - " file \"%s\" because it does not exist or permission" - " denied", privatekey)); - goto session_close; - } - - SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot import private key \"%s\"", - privatekey)); - - zabbix_log(LOG_LEVEL_DEBUG, "%s() failed to import private key \"%s\", rc:%d", - __func__, privatekey, rc); - - goto session_close; - } - - if (SSH_AUTH_SUCCESS != ssh_userauth_publickey(session, NULL, privkey)) - { - SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Public key authentication failed:" - " %s", ssh_get_error(session))); - goto session_close; - } - else - zabbix_log(LOG_LEVEL_DEBUG, "%s() authentication by public key succeeded", - __func__); - } - else - { - SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Unsupported authentication method." - " Supported methods: %s", userauthlist)); - goto session_close; - } - break; - } - - if (NULL == (channel = ssh_channel_new(session))) - { - SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot create generic session channel")); - goto session_close; - } - - while (SSH_OK != (rc = ssh_channel_open_session(channel))) - { - if (SSH_AGAIN != rc) - { - SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot establish generic session channel")); - goto channel_free; - } - } - - /* request a shell on a channel and execute command */ - dos2unix(item->params); /* CR+LF (Windows) => LF (Unix) */ - - while (SSH_OK != (rc = ssh_channel_request_exec(channel, item->params))) - { - if (SSH_AGAIN != rc) - { - SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot request a shell")); - goto channel_free; - } - } - - buffer = (char *)zbx_malloc(buffer, buf_size); - offset = 0; - - while (0 != (rc = ssh_channel_read(channel, tmp_buf, sizeof(tmp_buf), 0))) - { - if (rc < 0) - { - if (SSH_AGAIN == rc) - continue; - - SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot read data from SSH server")); - goto channel_close; - } - - if (MAX_EXECUTE_OUTPUT_LEN <= offset + rc) - { - SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Command output exceeded limit of %d KB", - MAX_EXECUTE_OUTPUT_LEN / ZBX_KIBIBYTE)); - goto channel_close; - } - - zbx_str_memcpy_alloc(&buffer, &buf_size, &offset, tmp_buf, rc); - } - - output = convert_to_utf8(buffer, offset, encoding); - zbx_rtrim(output, ZBX_WHITESPACE); - zbx_replace_invalid_utf8(output); - - SET_TEXT_RESULT(result, output); - output = NULL; - - ret = SYSINFO_RET_OK; -channel_close: - ssh_channel_close(channel); - zbx_free(buffer); -channel_free: - ssh_channel_free(channel); -session_close: - if (NULL != privkey) - ssh_key_free(privkey); - if (NULL != pubkey) - ssh_key_free(pubkey); - ssh_disconnect(session); -session_free: - ssh_free(session); -close: - zbx_free(publickey); - zbx_free(privatekey); - zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret)); - - return ret; -} -#endif - -#if defined(HAVE_SSH2) || defined(HAVE_SSH) int get_value_ssh(DC_ITEM *item, AGENT_RESULT *result) { AGENT_REQUEST request; int ret = NOTSUPPORTED; - const char *port, *encoding, *dns; + const char *port, *encoding, *dns, *ssh_options; init_request(&request); @@ -626,13 +36,15 @@ int get_value_ssh(DC_ITEM *item, AGENT_RESULT *result) goto out; } +#define SSH_RUN_KEY "ssh.run" if (0 != strcmp(SSH_RUN_KEY, get_rkey(&request))) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Unsupported item key for this item type.")); goto out; } +#undef SSH_RUN_KEY - if (4 < get_rparams_num(&request)) + if (5 < get_rparams_num(&request)) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters.")); goto out; @@ -656,8 +68,9 @@ int get_value_ssh(DC_ITEM *item, AGENT_RESULT *result) item->interface.port = ZBX_DEFAULT_SSH_PORT; encoding = get_rparam(&request, 3); + ssh_options = get_rparam(&request, 4); - ret = ssh_run(item, result, ZBX_NULL2EMPTY_STR(encoding)); + ret = ssh_run(item, result, ZBX_NULL2EMPTY_STR(encoding), ZBX_NULL2EMPTY_STR(ssh_options)); out: free_request(&request); diff --git a/src/zabbix_server/poller/ssh2_run.c b/src/zabbix_server/poller/ssh2_run.c new file mode 100644 index 00000000000..ef7e672c2ad --- /dev/null +++ b/src/zabbix_server/poller/ssh2_run.c @@ -0,0 +1,477 @@ +/* +** Zabbix +** Copyright (C) 2001-2023 Zabbix SIA +** +** 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 2 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 "ssh_run.h" + +#include + +#include "comms.h" +#include "log.h" + +#if !defined(HAVE_LIBSSH2_METHOD_KEX) && !defined(HAVE_LIBSSH2_METHOD_HOSTKEY) && \ + !defined(HAVE_LIBSSH2_METHOD_CRYPT_CS) && !defined(HAVE_LIBSSH2_METHOD_CRYPT_SC) && \ + !defined(HAVE_LIBSSH2_METHOD_MAC_CS) && !defined(HAVE_LIBSSH2_METHOD_MAC_SC) +#define HAVE_NO_LIBSSH2_METHODS 1 +#endif + +/* the size of temporary buffer used to read from data channel */ +#define DATA_BUFFER_SIZE 4096 + +extern char *CONFIG_SOURCE_IP; +extern char *CONFIG_SSH_KEY_LOCATION; + +static const char *password; + +#ifndef HAVE_NO_LIBSSH2_METHODS +static int ssh_set_options(LIBSSH2_SESSION *session, int type, const char *key_str, const char *value, + char **err_msg) +{ + int res, ret = SUCCEED; + + zabbix_log(LOG_LEVEL_DEBUG, "In %s() key_str:'%s' value:'%s'", __func__, key_str, value); + + if (0 > (res = libssh2_session_method_pref(session, type, value)) && res != LIBSSH2_ERROR_EAGAIN) + { + char *err; + const char **algs; + int rc; + + if (LIBSSH2_ERROR_NONE != libssh2_session_last_error(session, &err, NULL, 0)) + *err_msg = zbx_dsprintf(NULL, "Cannot set SSH option \"%s\": %s.", key_str, err); + else + *err_msg = zbx_dsprintf(NULL, "Cannot set SSH option \"%s\".", key_str); + + if (0 < (rc = libssh2_session_supported_algs(session, type, &algs))) + { + *err_msg = zbx_strdcat(*err_msg, " Supported values are: "); + + for (int i = 0; i < rc; i++) + { + *err_msg = zbx_strdcat(*err_msg, algs[i]); + + if (i < rc - 1) + *err_msg = zbx_strdcat(*err_msg, ", "); + } + *err_msg = zbx_strdcat(*err_msg, "."); + + libssh2_free(session, algs); + } + else + { + if (LIBSSH2_ERROR_NONE != libssh2_session_last_error(session, &err, NULL, 0)) + *err_msg = zbx_strdcatf(*err_msg, " Cannot get supported values: %s.", err); + else + *err_msg = zbx_strdcat(*err_msg, " Cannot get supported values."); + } + + ret = FAIL; + } + + zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret)); + + return ret; +} +#endif + +static int ssh_parse_options(LIBSSH2_SESSION *session, const char *options, char **err_msg) +{ + int ret = SUCCEED; + char opt_copy[1024] = {0}; + char *line, *saveptr; + + zbx_strscpy(opt_copy, options); + + for (line = strtok_r(opt_copy, ";", &saveptr); NULL != line; line = strtok_r(NULL, ";", &saveptr)) + { + char *eq_str = strchr(line, '='); + + if (NULL != eq_str) + *eq_str++ = '\0'; + + eq_str = ZBX_NULL2EMPTY_STR(eq_str); + +#ifdef HAVE_NO_LIBSSH2_METHODS + ZBX_UNUSED(session); + ZBX_UNUSED(eq_str); +#endif + +#ifdef HAVE_LIBSSH2_METHOD_KEX + if (0 == strncmp(line, KEY_EXCHANGE_STR, ZBX_CONST_STRLEN(KEY_EXCHANGE_STR))) + { + if (SUCCEED != (ret = ssh_set_options(session, LIBSSH2_METHOD_KEX, KEY_EXCHANGE_STR, eq_str, + err_msg))) + { + break; + } + continue; + } +#endif +#ifdef HAVE_LIBSSH2_METHOD_HOSTKEY + if (0 == strncmp(line, KEY_HOSTKEY_STR, ZBX_CONST_STRLEN(KEY_HOSTKEY_STR))) + { + if (SUCCEED != (ret = ssh_set_options(session, LIBSSH2_METHOD_HOSTKEY, KEY_HOSTKEY_STR, eq_str, + err_msg))) + { + break; + } + continue; + } +#endif +#if defined(HAVE_LIBSSH2_METHOD_CRYPT_CS) && defined(HAVE_LIBSSH2_METHOD_CRYPT_SC) + if (0 == strncmp(line, KEY_CIPHERS_STR, ZBX_CONST_STRLEN(KEY_CIPHERS_STR))) + { + if (SUCCEED != (ret = ssh_set_options(session, LIBSSH2_METHOD_CRYPT_CS, KEY_CIPHERS_STR, + eq_str, err_msg))) + { + break; + } + + if (SUCCEED != (ret = ssh_set_options(session, LIBSSH2_METHOD_CRYPT_SC, KEY_CIPHERS_STR, + eq_str, err_msg))) + { + break; + } + continue; + } +#endif +#if defined(HAVE_LIBSSH2_METHOD_MAC_CS) && defined(HAVE_LIBSSH2_METHOD_MAC_SC) + if (0 == strncmp(line, KEY_MACS_STR, ZBX_CONST_STRLEN(KEY_MACS_STR))) + { + if (SUCCEED != (ret = ssh_set_options(session, LIBSSH2_METHOD_MAC_CS, KEY_MACS_STR, eq_str, + err_msg))) + { + break; + } + + if (SUCCEED != (ret = ssh_set_options(session, LIBSSH2_METHOD_MAC_SC, KEY_MACS_STR, eq_str, + err_msg))) + { + break; + } + continue; + } +#endif + *err_msg = zbx_dsprintf(NULL, "SSH option \"%s\" is not supported.", line); + ret = FAIL; + break; + } + + return ret; +} +#undef HAVE_NO_LIBSSH2_METHODS + +static void kbd_callback(const char *name, int name_len, const char *instruction, + int instruction_len, int num_prompts, + const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts, + LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses, void **abstract) +{ + (void)name; + (void)name_len; + (void)instruction; + (void)instruction_len; + + if (num_prompts == 1) + { + responses[0].text = zbx_strdup(NULL, password); + responses[0].length = strlen(password); + } + + (void)prompts; + (void)abstract; +} + +static int waitsocket(int socket_fd, LIBSSH2_SESSION *session) +{ + struct timeval tv; + int rc, dir; + fd_set fd, *writefd = NULL, *readfd = NULL; + + tv.tv_sec = 10; + tv.tv_usec = 0; + + FD_ZERO(&fd); + FD_SET(socket_fd, &fd); + + /* now make sure we wait in the correct direction */ + dir = libssh2_session_block_directions(session); + + if (0 != (dir & LIBSSH2_SESSION_BLOCK_INBOUND)) + readfd = &fd; + + if (0 != (dir & LIBSSH2_SESSION_BLOCK_OUTBOUND)) + writefd = &fd; + + rc = select(socket_fd + 1, readfd, writefd, NULL, &tv); + + return rc; +} + +/* example ssh.run["ls /"] */ +int ssh_run(DC_ITEM *item, AGENT_RESULT *result, const char *encoding, const char *options) +{ + zbx_socket_t s; + LIBSSH2_SESSION *session; + LIBSSH2_CHANNEL *channel; + int auth_pw = 0, rc, ret = NOTSUPPORTED, exitcode; + char tmp_buf[DATA_BUFFER_SIZE], *userauthlist, *publickey = NULL, *privatekey = NULL, *ssherr, + *output, *buffer = NULL, *err_msg = NULL; + size_t offset = 0, buf_size = DATA_BUFFER_SIZE; + + zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); + + /* initializes an SSH session object */ + if (NULL == (session = libssh2_session_init())) + { + SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot initialize SSH session")); + goto ret_label; + } + + if (SUCCEED != ssh_parse_options(session, options, &err_msg)) + { + SET_MSG_RESULT(result, err_msg); + goto session_free; + } + + if (FAIL == zbx_tcp_connect(&s, CONFIG_SOURCE_IP, item->interface.addr, item->interface.port, 0, + ZBX_TCP_SEC_UNENCRYPTED, NULL, NULL)) + { + SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot connect to SSH server: %s", zbx_socket_strerror())); + goto session_free; + } + + /* set blocking mode on session */ + libssh2_session_set_blocking(session, 1); + + /* Create a session instance and start it up. This will trade welcome */ + /* banners, exchange keys, and setup crypto, compression, and MAC layers */ + if (0 != libssh2_session_startup(session, s.socket)) + { + libssh2_session_last_error(session, &ssherr, NULL, 0); + SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot establish SSH session: %s", ssherr)); + goto tcp_close; + } + + /* check what authentication methods are available */ + if (NULL != (userauthlist = libssh2_userauth_list(session, item->username, strlen(item->username)))) + { + if (NULL != strstr(userauthlist, "password")) + auth_pw |= 1; + if (NULL != strstr(userauthlist, "keyboard-interactive")) + auth_pw |= 2; + if (NULL != strstr(userauthlist, "publickey")) + auth_pw |= 4; + } + else + { + libssh2_session_last_error(session, &ssherr, NULL, 0); + SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot obtain authentication methods: %s", ssherr)); + goto session_close; + } + + zabbix_log(LOG_LEVEL_DEBUG, "%s() supported authentication methods:'%s'", __func__, userauthlist); + + switch (item->authtype) + { + case ITEM_AUTHTYPE_PASSWORD: + if (auth_pw & 1) + { + /* we could authenticate via password */ + if (0 != libssh2_userauth_password(session, item->username, item->password)) + { + libssh2_session_last_error(session, &ssherr, NULL, 0); + SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Password authentication failed: %s", + ssherr)); + goto session_close; + } + else + zabbix_log(LOG_LEVEL_DEBUG, "%s() password authentication succeeded", __func__); + } + else if (auth_pw & 2) + { + /* or via keyboard-interactive */ + password = item->password; + if (0 != libssh2_userauth_keyboard_interactive(session, item->username, &kbd_callback)) + { + libssh2_session_last_error(session, &ssherr, NULL, 0); + SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Keyboard-interactive authentication" + " failed: %s", ssherr)); + goto session_close; + } + else + zabbix_log(LOG_LEVEL_DEBUG, "%s() keyboard-interactive authentication succeeded", + __func__); + } + else + { + SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Unsupported authentication method." + " Supported methods: %s", userauthlist)); + goto session_close; + } + break; + case ITEM_AUTHTYPE_PUBLICKEY: + if (auth_pw & 4) + { + if (NULL == CONFIG_SSH_KEY_LOCATION) + { + SET_MSG_RESULT(result, zbx_strdup(NULL, "Authentication by public key failed." + " SSHKeyLocation option is not set")); + goto session_close; + } + + /* or by public key */ + publickey = zbx_dsprintf(publickey, "%s/%s", CONFIG_SSH_KEY_LOCATION, item->publickey); + privatekey = zbx_dsprintf(privatekey, "%s/%s", CONFIG_SSH_KEY_LOCATION, + item->privatekey); + + if (SUCCEED != zbx_is_regular_file(publickey)) + { + SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot access public key file %s", + publickey)); + goto session_close; + } + + if (SUCCEED != zbx_is_regular_file(privatekey)) + { + SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot access private key file %s", + privatekey)); + goto session_close; + } + + rc = libssh2_userauth_publickey_fromfile(session, item->username, publickey, + privatekey, item->password); + + if (0 != rc) + { + libssh2_session_last_error(session, &ssherr, NULL, 0); + SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Public key authentication failed:" + " %s", ssherr)); + goto session_close; + } + else + zabbix_log(LOG_LEVEL_DEBUG, "%s() authentication by public key succeeded", + __func__); + } + else + { + SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Unsupported authentication method." + " Supported methods: %s", userauthlist)); + goto session_close; + } + break; + } + + /* exec non-blocking on the remove host */ + while (NULL == (channel = libssh2_channel_open_session(session))) + { + switch (libssh2_session_last_error(session, NULL, NULL, 0)) + { + /* marked for non-blocking I/O but the call would block. */ + case LIBSSH2_ERROR_EAGAIN: + waitsocket(s.socket, session); + continue; + default: + SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot establish generic session channel")); + goto session_close; + } + } + + dos2unix(item->params); /* CR+LF (Windows) => LF (Unix) */ + /* request a shell on a channel and execute command */ + while (0 != (rc = libssh2_channel_exec(channel, item->params))) + { + switch (rc) + { + case LIBSSH2_ERROR_EAGAIN: + waitsocket(s.socket, session); + continue; + default: + SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot request a shell")); + goto channel_close; + } + } + + buffer = (char *)zbx_malloc(buffer, buf_size); + + while (0 != (rc = libssh2_channel_read(channel, tmp_buf, sizeof(tmp_buf)))) + { + if (rc < 0) + { + if (LIBSSH2_ERROR_EAGAIN == rc) + waitsocket(s.socket, session); + + SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot read data from SSH server")); + goto channel_close; + } + + if (MAX_EXECUTE_OUTPUT_LEN <= offset + (size_t)rc) + { + SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Command output exceeded limit of %d KB", + MAX_EXECUTE_OUTPUT_LEN / ZBX_KIBIBYTE)); + goto channel_close; + } + + zbx_str_memcpy_alloc(&buffer, &buf_size, &offset, tmp_buf, (size_t)rc); + } + + output = convert_to_utf8(buffer, offset, encoding); + zbx_rtrim(output, ZBX_WHITESPACE); + zbx_replace_invalid_utf8(output); + + SET_TEXT_RESULT(result, output); + output = NULL; + + ret = SYSINFO_RET_OK; + +channel_close: + /* close an active data channel */ + exitcode = 127; + while (LIBSSH2_ERROR_EAGAIN == (rc = libssh2_channel_close(channel))) + waitsocket(s.socket, session); + + zbx_free(buffer); + + if (0 != rc) + { + libssh2_session_last_error(session, &ssherr, NULL, 0); + zabbix_log(LOG_LEVEL_WARNING, "%s() cannot close generic session channel: %s", __func__, ssherr); + } + else + exitcode = libssh2_channel_get_exit_status(channel); + + zabbix_log(LOG_LEVEL_DEBUG, "%s() exitcode:%d bytecount:" ZBX_FS_SIZE_T, __func__, exitcode, offset); + + libssh2_channel_free(channel); + channel = NULL; + +session_close: + libssh2_session_disconnect(session, "Normal Shutdown"); + +tcp_close: + zbx_tcp_close(&s); + +session_free: + libssh2_session_free(session); + +ret_label: + zbx_free(publickey); + zbx_free(privatekey); + zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret)); + + return ret; +} diff --git a/src/zabbix_server/poller/ssh_run.c b/src/zabbix_server/poller/ssh_run.c new file mode 100644 index 00000000000..88beaa6497b --- /dev/null +++ b/src/zabbix_server/poller/ssh_run.c @@ -0,0 +1,443 @@ +/* +** Zabbix +** Copyright (C) 2001-2023 Zabbix SIA +** +** 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 2 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 "ssh_run.h" + +#include "common.h" + +#include + +#include "comms.h" +#include "log.h" + +#if !defined(HAVE_SSH_OPTIONS_KEY_EXCHANGE) && !defined(HAVE_SSH_OPTIONS_HOSTKEYS) && \ + !defined(HAVE_SSH_OPTIONS_CIPHERS_C_S) && !defined(HAVE_SSH_OPTIONS_CIPHERS_S_C) && \ + !defined(HAVE_SSH_OPTIONS_HMAC_C_S) && !defined(HAVE_SSH_OPTIONS_HMAC_S_C) +#define HAVE_NO_SSH_OPTIONS 1 +#endif + +/* the size of temporary buffer used to read from data channel */ +#define DATA_BUFFER_SIZE 4096 + +extern char *CONFIG_SOURCE_IP; +extern char *CONFIG_SSH_KEY_LOCATION; + +#ifndef HAVE_NO_SSH_OPTIONS +static int ssh_set_options(ssh_session session, enum ssh_options_e type, const char *key_str, const char *value, + char **err_msg) +{ + int ret = SUCCEED; + + zabbix_log(LOG_LEVEL_DEBUG, "In %s() key_str:'%s' value:'%s'", __func__, key_str, value); + + if (0 > ssh_options_set(session, type, value)) + { + *err_msg = zbx_dsprintf(NULL, "Cannot set SSH option \"%s\": %s.", key_str, ssh_get_error(session)); + + ret = FAIL; + } + + zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret)); + + return ret; +} +#endif + +static int ssh_parse_options(ssh_session session, const char *options, char **err_msg) +{ + int ret = SUCCEED; + char opt_copy[1024] = {0}; + char *line, *saveptr; + + zbx_strscpy(opt_copy, options); + + for (line = strtok_r(opt_copy, ";", &saveptr); NULL != line; line = strtok_r(NULL, ";", &saveptr)) + { + char *eq_str = strchr(line, '='); + + if (NULL != eq_str) + *eq_str++ = '\0'; + + eq_str = ZBX_NULL2EMPTY_STR(eq_str); + +#ifdef HAVE_NO_SSH_OPTIONS + ZBX_UNUSED(session); + ZBX_UNUSED(eq_str); +#endif + +#ifdef HAVE_SSH_OPTIONS_KEY_EXCHANGE + if (0 == strncmp(line, KEY_EXCHANGE_STR, ZBX_CONST_STRLEN(KEY_EXCHANGE_STR))) + { + if (SUCCEED != (ret = ssh_set_options(session, SSH_OPTIONS_KEY_EXCHANGE, KEY_EXCHANGE_STR, + eq_str, err_msg))) + { + break; + } + continue; + } +#endif +#ifdef HAVE_SSH_OPTIONS_HOSTKEYS + if (0 == strncmp(line, KEY_HOSTKEY_STR, ZBX_CONST_STRLEN(KEY_HOSTKEY_STR))) + { + if (SUCCEED != (ret = ssh_set_options(session, SSH_OPTIONS_HOSTKEYS, KEY_HOSTKEY_STR, eq_str, + err_msg))) + { + break; + } + continue; + } +#endif +#if defined(HAVE_SSH_OPTIONS_CIPHERS_C_S) && defined(HAVE_SSH_OPTIONS_CIPHERS_S_C) + if (0 == strncmp(line, KEY_CIPHERS_STR, ZBX_CONST_STRLEN(KEY_CIPHERS_STR))) + { + if (SUCCEED != (ret = ssh_set_options(session, SSH_OPTIONS_CIPHERS_C_S, KEY_CIPHERS_STR, + eq_str, err_msg))) + { + break; + } + + if (SUCCEED != (ret = ssh_set_options(session, SSH_OPTIONS_CIPHERS_S_C, KEY_CIPHERS_STR, + eq_str, err_msg))) + { + break; + } + continue; + } +#endif +#if defined(HAVE_SSH_OPTIONS_HMAC_C_S) && defined(HAVE_SSH_OPTIONS_HMAC_S_C) + if (0 == strncmp(line, KEY_MACS_STR, ZBX_CONST_STRLEN(KEY_MACS_STR))) + { + if (SUCCEED != (ret = ssh_set_options(session, SSH_OPTIONS_HMAC_C_S, KEY_MACS_STR, eq_str, + err_msg))) + { + break; + } + + if (SUCCEED != (ret = ssh_set_options(session, SSH_OPTIONS_HMAC_S_C, KEY_MACS_STR, eq_str, + err_msg))) + { + break; + } + continue; + } +#endif + *err_msg = zbx_dsprintf(NULL, "SSH option \"%s\" is not supported.", line); + ret = FAIL; + break; + } + + return ret; +} +#undef HAVE_NO_SSH_OPTIONS + +/* example ssh.run["ls /"] */ +int ssh_run(DC_ITEM *item, AGENT_RESULT *result, const char *encoding, const char *options) +{ + ssh_session session; + ssh_channel channel; + ssh_key privkey = NULL, pubkey = NULL; + int rc, userauth, ret = NOTSUPPORTED; + char *output, *publickey = NULL, *privatekey = NULL, *buffer = NULL, *err_msg = NULL; + char tmp_buf[DATA_BUFFER_SIZE], userauthlist[64]; + size_t offset = 0, buf_size = DATA_BUFFER_SIZE; + + zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); + + /* initializes an SSH session object */ + if (NULL == (session = ssh_new())) + { + SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot initialize SSH session")); + zabbix_log(LOG_LEVEL_DEBUG, "Cannot initialize SSH session"); + + goto close; + } + + /* set blocking mode on session */ + ssh_set_blocking(session, 1); + + /* create a session instance and start it up */ + if (0 != ssh_options_set(session, SSH_OPTIONS_HOST, item->interface.addr) || + 0 != ssh_options_set(session, SSH_OPTIONS_PORT, &item->interface.port) || + 0 != ssh_options_set(session, SSH_OPTIONS_USER, item->username)) + { + SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot set SSH session options: %s", + ssh_get_error(session))); + goto session_free; + } + + if (0 < strlen(options)) + { + int proc_config = 0; + + if (0 != ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &proc_config)) + { + SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot turn off SSH default config processing: %s", + ssh_get_error(session))); + goto session_free; + } + + if (SUCCEED != ssh_parse_options(session, options, &err_msg)) + { + SET_MSG_RESULT(result, err_msg); + goto session_free; + } + } + + if (SSH_OK != ssh_connect(session)) + { + SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot establish SSH session: %s", ssh_get_error(session))); + goto session_free; + } + + /* check which authentication methods are available */ + if (SSH_AUTH_ERROR == ssh_userauth_none(session, NULL)) + { + SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Error during authentication: %s", ssh_get_error(session))); + goto session_close; + } + + userauthlist[0] = '\0'; + + if (0 != (userauth = ssh_userauth_list(session, NULL))) + { + if (0 != (userauth & SSH_AUTH_METHOD_NONE)) + offset += zbx_snprintf(userauthlist + offset, sizeof(userauthlist) - offset, "none, "); + if (0 != (userauth & SSH_AUTH_METHOD_PASSWORD)) + offset += zbx_snprintf(userauthlist + offset, sizeof(userauthlist) - offset, "password, "); + if (0 != (userauth & SSH_AUTH_METHOD_INTERACTIVE)) + offset += zbx_snprintf(userauthlist + offset, sizeof(userauthlist) - offset, + "keyboard-interactive, "); + if (0 != (userauth & SSH_AUTH_METHOD_PUBLICKEY)) + offset += zbx_snprintf(userauthlist + offset, sizeof(userauthlist) - offset, "publickey, "); + if (0 != (userauth & SSH_AUTH_METHOD_HOSTBASED)) + offset += zbx_snprintf(userauthlist + offset, sizeof(userauthlist) - offset, "hostbased, "); + if (2 <= offset) + userauthlist[offset-2] = '\0'; + } + + zabbix_log(LOG_LEVEL_DEBUG, "%s() supported authentication methods: %s", __func__, userauthlist); + + switch (item->authtype) + { + case ITEM_AUTHTYPE_PASSWORD: + if (0 != (userauth & SSH_AUTH_METHOD_PASSWORD)) + { + /* we could authenticate via password */ + if (SSH_AUTH_SUCCESS != ssh_userauth_password(session, NULL, item->password)) + { + SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Password authentication failed: %s", + ssh_get_error(session))); + goto session_close; + } + else + zabbix_log(LOG_LEVEL_DEBUG, "%s() password authentication succeeded", __func__); + } + else if (0 != (userauth & SSH_AUTH_METHOD_INTERACTIVE)) + { + /* or via keyboard-interactive */ + while (SSH_AUTH_INFO == (rc = ssh_userauth_kbdint(session, item->username, NULL))) + { + if (1 == ssh_userauth_kbdint_getnprompts(session) && + 0 != ssh_userauth_kbdint_setanswer(session, 0, item->password)) + { + zabbix_log(LOG_LEVEL_DEBUG,"Cannot set answer: %s", + ssh_get_error(session)); + } + } + + if (SSH_AUTH_SUCCESS != rc) + { + SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Keyboard-interactive authentication" + " failed: %s", ssh_get_error(session))); + goto session_close; + } + else + { + zabbix_log(LOG_LEVEL_DEBUG, "%s() keyboard-interactive authentication" + " succeeded", __func__); + } + } + else + { + SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Unsupported authentication method." + " Supported methods: %s", userauthlist)); + goto session_close; + } + break; + case ITEM_AUTHTYPE_PUBLICKEY: + if (0 != (userauth & SSH_AUTH_METHOD_PUBLICKEY)) + { + if (NULL == CONFIG_SSH_KEY_LOCATION) + { + SET_MSG_RESULT(result, zbx_strdup(NULL, "Authentication by public key failed." + " SSHKeyLocation option is not set")); + goto session_close; + } + + /* or by public key */ + publickey = zbx_dsprintf(publickey, "%s/%s", CONFIG_SSH_KEY_LOCATION, item->publickey); + privatekey = zbx_dsprintf(privatekey, "%s/%s", CONFIG_SSH_KEY_LOCATION, + item->privatekey); + + if (SUCCEED != zbx_is_regular_file(publickey)) + { + SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot access public key file %s", + publickey)); + goto session_close; + } + + if (SUCCEED != zbx_is_regular_file(privatekey)) + { + SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot access private key file %s", + privatekey)); + goto session_close; + } + + if (SSH_OK != ssh_pki_import_pubkey_file(publickey, &pubkey)) + { + SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Failed to import public key: %s", + ssh_get_error(session))); + goto session_close; + } + + if (SSH_AUTH_SUCCESS != ssh_userauth_try_publickey(session, NULL, pubkey)) + { + SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Public key try failed: %s", + ssh_get_error(session))); + goto session_close; + } + + if (SSH_OK != (rc = ssh_pki_import_privkey_file(privatekey, item->password, NULL, NULL, + &privkey))) + { + if (SSH_EOF == rc) + { + SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot import private key" + " file \"%s\" because it does not exist or permission" + " denied", privatekey)); + goto session_close; + } + + SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot import private key \"%s\"", + privatekey)); + + zabbix_log(LOG_LEVEL_DEBUG, "%s() failed to import private key \"%s\", rc:%d", + __func__, privatekey, rc); + + goto session_close; + } + + if (SSH_AUTH_SUCCESS != ssh_userauth_publickey(session, NULL, privkey)) + { + SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Public key authentication failed:" + " %s", ssh_get_error(session))); + goto session_close; + } + else + zabbix_log(LOG_LEVEL_DEBUG, "%s() authentication by public key succeeded", + __func__); + } + else + { + SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Unsupported authentication method." + " Supported methods: %s", userauthlist)); + goto session_close; + } + break; + } + + if (NULL == (channel = ssh_channel_new(session))) + { + SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot create generic session channel")); + goto session_close; + } + + while (SSH_OK != (rc = ssh_channel_open_session(channel))) + { + if (SSH_AGAIN != rc) + { + SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot establish generic session channel")); + goto channel_free; + } + } + + /* request a shell on a channel and execute command */ + dos2unix(item->params); /* CR+LF (Windows) => LF (Unix) */ + + while (SSH_OK != (rc = ssh_channel_request_exec(channel, item->params))) + { + if (SSH_AGAIN != rc) + { + SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot request a shell")); + goto channel_free; + } + } + + buffer = (char *)zbx_malloc(buffer, buf_size); + offset = 0; + + while (0 != (rc = ssh_channel_read(channel, tmp_buf, sizeof(tmp_buf), 0))) + { + if (rc < 0) + { + if (SSH_AGAIN == rc) + continue; + + SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot read data from SSH server")); + goto channel_close; + } + + if (MAX_EXECUTE_OUTPUT_LEN <= offset + (size_t)rc) + { + SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Command output exceeded limit of %d KB", + MAX_EXECUTE_OUTPUT_LEN / ZBX_KIBIBYTE)); + goto channel_close; + } + + zbx_str_memcpy_alloc(&buffer, &buf_size, &offset, tmp_buf, (size_t)rc); + } + + output = convert_to_utf8(buffer, offset, encoding); + zbx_rtrim(output, ZBX_WHITESPACE); + zbx_replace_invalid_utf8(output); + + SET_TEXT_RESULT(result, output); + output = NULL; + + ret = SYSINFO_RET_OK; +channel_close: + ssh_channel_close(channel); + zbx_free(buffer); +channel_free: + ssh_channel_free(channel); +session_close: + if (NULL != privkey) + ssh_key_free(privkey); + if (NULL != pubkey) + ssh_key_free(pubkey); + ssh_disconnect(session); +session_free: + ssh_free(session); +close: + zbx_free(publickey); + zbx_free(privatekey); + zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret)); + + return ret; +} diff --git a/src/zabbix_server/poller/ssh_run.h b/src/zabbix_server/poller/ssh_run.h new file mode 100644 index 00000000000..c23398b6277 --- /dev/null +++ b/src/zabbix_server/poller/ssh_run.h @@ -0,0 +1,37 @@ +/* +** Zabbix +** Copyright (C) 2001-2023 Zabbix SIA +** +** 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 2 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. +**/ + +#ifndef ZABBIX_SSH_RUN_H +#define ZABBIX_SSH_RUN_H + +#include "config.h" +#include "module.h" + +#if defined(HAVE_SSH2) || defined(HAVE_SSH) +#include "dbcache.h" + +#define KEY_EXCHANGE_STR "KexAlgorithms" +#define KEY_HOSTKEY_STR "HostkeyAlgorithms" +#define KEY_CIPHERS_STR "Ciphers" +#define KEY_MACS_STR "MACs" + +int ssh_run(DC_ITEM *item, AGENT_RESULT *result, const char *encoding, const char *options); +#endif /* defined(HAVE_SSH2) || defined(HAVE_SSH)*/ + +#endif diff --git a/ui/include/defines.inc.php b/ui/include/defines.inc.php index 8a36deb5832..1f2d52b4b72 100644 --- a/ui/include/defines.inc.php +++ b/ui/include/defines.inc.php @@ -547,7 +547,7 @@ define('ITEM_DATA_TYPE_BOOLEAN', 3); define('ZBX_DEFAULT_KEY_DB_MONITOR', 'db.odbc.select[,,]'); define('ZBX_DEFAULT_KEY_DB_MONITOR_DISCOVERY', 'db.odbc.discovery[,,]'); -define('ZBX_DEFAULT_KEY_SSH', 'ssh.run[,,,]'); +define('ZBX_DEFAULT_KEY_SSH', 'ssh.run[,,,,]'); define('ZBX_DEFAULT_KEY_TELNET', 'telnet.run[,,,]'); define('ZBX_DEFAULT_JMX_ENDPOINT', 'service:jmx:rmi:///jndi/rmi://{HOST.CONN}:{HOST.PORT}/jmxrmi'); diff --git a/ui/tests/selenium/items/testFormItem.php b/ui/tests/selenium/items/testFormItem.php index 5a892b87d42..549e53e5e6a 100644 --- a/ui/tests/selenium/items/testFormItem.php +++ b/ui/tests/selenium/items/testFormItem.php @@ -427,7 +427,7 @@ class testFormItem extends CLegacyWebTest { } if ($type == 'SSH agent' && !isset($itemid)) { - $this->zbxTestAssertElementValue('key', 'ssh.run[,,,]'); + $this->zbxTestAssertElementValue('key', 'ssh.run[,,,,]'); } if ($type == 'TELNET agent' && !isset($itemid)) { diff --git a/ui/tests/selenium/items/testFormItemPrototype.php b/ui/tests/selenium/items/testFormItemPrototype.php index 43d0bbb2f1d..f3cbfda2145 100644 --- a/ui/tests/selenium/items/testFormItemPrototype.php +++ b/ui/tests/selenium/items/testFormItemPrototype.php @@ -618,7 +618,7 @@ class testFormItemPrototype extends CLegacyWebTest { } if ($type == 'SSH agent' && !isset($itemid)) { - $this->zbxTestAssertElementValue('key', 'ssh.run[,,,]'); + $this->zbxTestAssertElementValue('key', 'ssh.run[,,,,]'); } if ($type == 'TELNET agent' && !isset($itemid)) { diff --git a/ui/tests/selenium/lld/testFormLowLevelDiscovery.php b/ui/tests/selenium/lld/testFormLowLevelDiscovery.php index 6417a6e6d19..3741fded76d 100644 --- a/ui/tests/selenium/lld/testFormLowLevelDiscovery.php +++ b/ui/tests/selenium/lld/testFormLowLevelDiscovery.php @@ -271,7 +271,7 @@ class testFormLowLevelDiscovery extends CLegacyWebTest { if (!isset($data['form'])) { switch($type) { case 'SSH agent': - $this->zbxTestAssertElementValue('key', 'ssh.run[,,,]'); + $this->zbxTestAssertElementValue('key', 'ssh.run[,,,,]'); break; case 'TELNET agent': $this->zbxTestAssertElementValue('key', 'telnet.run[,,,]'); -- 2.30.2