epan/dissectors/pidl/ C99 drsuapi
[wireshark-sm.git] / extcap / ssh-base.c
blob0fc7db19829d902340f87145af3d03cf0e053342
1 /* ssh-base.c
2 * ssh-base has base utility functions to connect to hosts via ssh
4 * Copyright 2016, Dario Lombardo
6 * Wireshark - Network traffic analyzer
7 * By Gerald Combs <gerald@wireshark.org>
8 * Copyright 1998 Gerald Combs
10 * SPDX-License-Identifier: GPL-2.0-or-later
13 #include "config.h"
14 #define WS_LOG_DOMAIN LOG_DOMAIN_EXTCAP
16 #include "ssh-base.h"
18 #include <extcap/extcap-base.h>
19 #include <string.h>
20 #include <libssh/callbacks.h>
21 #include <ws_attributes.h>
22 #include <wsutil/wslog.h>
25 * The unreleased 0.11.0 version of libssh has the ability to
26 * add algorithms to the default supported list by prepending
27 * "+" to the configuration list. For older versions, we have
28 * to specify all the algorithms we want, but as long as at
29 * least one succeeds the command won't fail. (That means that
30 * it's possible that we won't actually add support for SHA-1,
31 * say if it's running on a system in FIPS mode. We could parse
32 * the returned list to check.)
34 #if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0,11,0)
35 #define HOSTKEYS_SHA1 "+ssh-rsa"
36 #define KEY_EXCHANGE_SHA1 "+diffie-hellman-group14-sha1,diffie-hellman-group1-sha1,diffie-hellman-group-exchange-sha1"
37 #define HMAC_SHA1 "+hmac-sha1-etm@openssh.com,hmac-sha1"
38 #else
39 #define HOSTKEYS_SHA1 \
40 "ssh-ed25519," \
41 "ecdsa-sha2-nistp521," \
42 "ecdsa-sha2-nistp384," \
43 "ecdsa-sha2-nistp256," \
44 "sk-ssh-ed25519@openssh.com," \
45 "sk-ecdsa-sha2-nistp256@openssh.com," \
46 "rsa-sha2-512," \
47 "rsa-sha2-256," \
48 "ssh-rsa"
49 #define KEY_EXCHANGE_SHA1 \
50 "curve25519-sha256,curve25519-sha256@libssh.org," \
51 "ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521," \
52 "diffie-hellman-group18-sha512,diffie-hellman-group16-sha512," \
53 "diffie-hellman-group-exchange-sha256," \
54 "diffie-hellman-group14-sha256," \
55 "diffie-hellman-group-exchange-sha1," \
56 "diffie-hellman-group14-sha1,diffie-hellman-group1-sha1"
57 #define HMAC_SHA1 \
58 "hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com," \
59 "hmac-sha2-256,hmac-sha2-512," \
60 "hmac-sha1-etm@openssh.com,hmac-sha1"
61 #endif
63 static void extcap_log(int priority, const char *function, const char *buffer, void *userdata _U_)
65 enum ws_log_level level = LOG_LEVEL_DEBUG;
66 switch (priority) {
67 case SSH_LOG_TRACE:
68 level = LOG_LEVEL_NOISY;
69 break;
70 case SSH_LOG_DEBUG:
71 level = LOG_LEVEL_DEBUG;
72 break;
73 case SSH_LOG_INFO:
74 level = LOG_LEVEL_INFO;
75 break;
76 case SSH_LOG_WARN:
77 default:
78 /* Prior to 0.11.0 libssh prints far too much at SSH_LOG_WARN,
79 * including merely informational messages.
80 * Lower them to LOG_LEVEL_INFO, which won't get shown in the GUI
81 * and aren't shown by default. (Anything INFO and below goes to
82 * stdout due to ws_log_console_writer_set_use_stdout in extcap-base.c)
83 * After the following commit libssh only uses LOG_LEVEL_WARN for
84 * serious issues:
85 * https://gitlab.com/libssh/libssh-mirror/-/commit/657d9143d121dfff74f5a63f734d0096c7f37194
87 #if LIBSSH_VERSION_INT < SSH_VERSION_INT(0,11,0)
88 level = LOG_LEVEL_INFO;
89 #else
90 level = LOG_LEVEL_WARNING;
91 #endif
92 break;
94 /* We set the libssh log level to specifically ask for this, so don't
95 * both checking the log level a second time.
97 ws_log_write_always_full("libssh", level, NULL, 0, function, "%s", buffer);
100 void add_libssh_info(extcap_parameters * extcap_conf)
102 extcap_base_set_compiled_with(extcap_conf, "libssh version %s", SSH_STRINGIFY(LIBSSH_VERSION));
103 extcap_base_set_running_with(extcap_conf, "libssh version %s", ssh_version(0));
106 ssh_session create_ssh_connection(const ssh_params_t* ssh_params, char** err_info)
108 ssh_session sshs;
109 char* username = NULL;
110 unsigned port;
112 /* Open session and set options */
113 sshs = ssh_new();
114 if (sshs == NULL) {
115 *err_info = g_strdup("Can't create ssh session");
116 return NULL;
119 if (!ssh_params->host) {
120 *err_info = g_strdup("Hostname needed");
121 goto failure;
124 if (ssh_options_set(sshs, SSH_OPTIONS_HOST, ssh_params->host)) {
125 *err_info = ws_strdup_printf("Can't set the host: %s", ssh_params->host);
126 goto failure;
129 /* Load the configurations already present in the system configuration file. */
130 /* They will be overwritten by the user-provided configurations. */
131 if (ssh_options_parse_config(sshs, NULL) != 0) {
132 *err_info = g_strdup("Unable to load the configuration file");
133 goto failure;
136 ssh_options_set(sshs, SSH_OPTIONS_LOG_VERBOSITY, &ssh_params->debug);
137 ssh_set_log_callback(extcap_log);
139 if (ssh_params->ssh_sha1) {
140 if (ssh_options_set(sshs, SSH_OPTIONS_HOSTKEYS, HOSTKEYS_SHA1)) {
141 *err_info = ws_strdup_printf("Can't set host keys to allow SHA-1.");
142 goto failure;
144 if (ssh_options_set(sshs, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, HOSTKEYS_SHA1)) {
145 *err_info = ws_strdup_printf("Can't set public key algorithms to allow SSH-RSA (SHA-1).");
146 goto failure;
148 if (ssh_options_set(sshs, SSH_OPTIONS_KEY_EXCHANGE, KEY_EXCHANGE_SHA1)) {
149 *err_info = ws_strdup_printf("Can't set key exchange methods to allow SHA-1.");
150 goto failure;
152 if (ssh_options_set(sshs, SSH_OPTIONS_HMAC_C_S, HMAC_SHA1)) {
153 *err_info = ws_strdup_printf("Can't set MAC client to server algorithms to allow SHA-1.");
154 goto failure;
156 if (ssh_options_set(sshs, SSH_OPTIONS_HMAC_S_C, HMAC_SHA1)) {
157 *err_info = ws_strdup_printf("Can't set MAC server to client algorithms to allow SHA-1.");
158 goto failure;
162 if (ssh_params->port != 0) {
163 port = ssh_params->port;
164 if (ssh_options_set(sshs, SSH_OPTIONS_PORT, &port)) {
165 *err_info = ws_strdup_printf("Can't set the port: %u", port);
166 goto failure;
170 if (ssh_params->proxycommand) {
171 #if defined(_WIN32) && (LIBSSH_VERSION_INT < SSH_VERSION_INT(0,11,0))
172 // ProxyCommand isn't supported on Windows:
173 // https://gitlab.com/libssh/libssh-mirror/-/issues/75
174 // ssh_options_set "succeeds" in setting it, though.
175 // On 0.11.0 and later, ssh_connect will fail.
176 // Before 0.11.0, libssh silently ignores the option.
177 // https://gitlab.com/libssh/libssh-mirror/-/commit/bed4438695df2f4635617fc45f802d782fdd8479
178 *err_info = ws_strdup_printf("Can't set the ProxyCommand: %s (libssh does not support proxy commands on Windows.)", ssh_params->proxycommand);
179 goto failure;
180 #endif
181 if (ssh_options_set(sshs, SSH_OPTIONS_PROXYCOMMAND, ssh_params->proxycommand)) {
182 *err_info = ws_strdup_printf("Can't set the ProxyCommand: %s", ssh_params->proxycommand);
183 goto failure;
187 if (ssh_params->username) {
188 if (ssh_options_set(sshs, SSH_OPTIONS_USER, ssh_params->username)) {
189 *err_info = ws_strdup_printf("Can't set the username: %s", ssh_params->username);
190 goto failure;
194 ssh_options_get(sshs, SSH_OPTIONS_USER, &username);
195 ssh_options_get_port(sshs, &port);
197 ws_log(LOG_DOMAIN_CAPCHILD, LOG_LEVEL_INFO, "Opening ssh connection to %s@%s:%u", username,
198 ssh_params->host, port);
200 ssh_string_free_char(username);
202 /* Connect to server */
203 if (ssh_connect(sshs) != SSH_OK) {
204 *err_info = ws_strdup_printf("Connection error: %s", ssh_get_error(sshs));
205 goto failure;
208 /* If a public key path has been provided, try to authenticate using it */
209 if (ssh_params->sshkey_path) {
210 ssh_key pkey = ssh_key_new();
211 int ret;
213 ws_info("Connecting using public key in %s...", ssh_params->sshkey_path);
214 ret = ssh_pki_import_privkey_file(ssh_params->sshkey_path, ssh_params->sshkey_passphrase, NULL, NULL, &pkey);
216 switch (ret) {
218 case SSH_OK:
219 if (ssh_userauth_publickey(sshs, NULL, pkey) == SSH_AUTH_SUCCESS) {
220 ws_info("done");
221 ssh_key_free(pkey);
222 return sshs;
224 ws_info("failed (%s)", ssh_get_error(sshs));
225 break;
226 case SSH_EOF:
227 ws_warning("Error importing key from %s. File doesn't exist or permission denied.",
228 ssh_params->sshkey_path);
229 break;
230 case SSH_ERROR:
231 /* Unfortunately we can't call ssh_get_error() on the
232 * key to determine why import failed.
234 ws_warning("Error importing key from %s. Make sure it is a valid"
235 " private key file and any necessary passphrase is configured.",
236 ssh_params->sshkey_path);
237 break;
238 default:
239 ws_warning("Unknown error from ssh_pki_import_privkey_file");
241 ssh_key_free(pkey);
244 /* Workaround: it may happen that libssh closes socket in meantime and any next ssh_ call fails so we should detect it in advance */
245 if (ssh_get_fd(sshs) != (socket_t)-1) {
246 /* If a password has been provided and all previous attempts failed, try to use it */
247 if (ssh_params->password) {
248 ws_info("Connecting using password...");
249 if (ssh_userauth_password(sshs, ssh_params->username, ssh_params->password) == SSH_AUTH_SUCCESS) {
250 ws_info("done");
251 return sshs;
253 ws_info("failed");
255 } else {
256 ws_info("ssh connection closed before password authentication");
259 /* Workaround: it may happen that libssh closes socket in meantime and any next ssh_ call fails so we should detect it in advance */
260 if (ssh_get_fd(sshs) != (socket_t)-1) {
261 /* Try to authenticate using standard public key */
262 ws_info("Connecting using standard public key...");
263 if (ssh_userauth_publickey_auto(sshs, NULL, NULL) == SSH_AUTH_SUCCESS) {
264 ws_info("done");
265 return sshs;
267 ws_info("failed");
268 } else {
269 ws_info("ssh connection closed before public key authentication");
272 *err_info = ws_strdup_printf("Can't find a valid authentication. Disconnecting.");
274 /* All authentication failed. Disconnect and return */
275 ssh_disconnect(sshs);
277 failure:
278 ssh_free(sshs);
279 return NULL;
282 int ssh_channel_printf(ssh_channel channel, const char* fmt, ...)
284 char* buf;
285 va_list arg;
286 int ret = EXIT_SUCCESS;
288 va_start(arg, fmt);
289 buf = ws_strdup_vprintf(fmt, arg);
290 ws_debug("%s", buf);
291 if (ssh_channel_write(channel, buf, (uint32_t)strlen(buf)) == SSH_ERROR)
292 ret = EXIT_FAILURE;
293 va_end(arg);
294 g_free(buf);
296 return ret;
299 void ssh_cleanup(ssh_session* sshs, ssh_channel* channel)
301 if (*channel) {
302 ssh_channel_send_eof(*channel);
303 ssh_channel_close(*channel);
304 ssh_channel_free(*channel);
305 *channel = NULL;
308 if (*sshs) {
309 ssh_disconnect(*sshs);
310 ssh_free(*sshs);
311 *sshs = NULL;
315 ssh_params_t* ssh_params_new(void)
317 return g_new0(ssh_params_t, 1);
320 void ssh_params_free(ssh_params_t* ssh_params)
322 if (!ssh_params)
323 return;
324 g_free(ssh_params->host);
325 g_free(ssh_params->username);
326 g_free(ssh_params->password);
327 g_free(ssh_params->sshkey_path);
328 g_free(ssh_params->sshkey_passphrase);
329 g_free(ssh_params->proxycommand);
330 g_free(ssh_params);
333 void ssh_params_set_log_level(ssh_params_t* ssh_params, enum ws_log_level level)
335 switch (level) {
336 case LOG_LEVEL_NOISY:
337 ssh_params->debug = SSH_LOG_TRACE;
338 break;
339 case LOG_LEVEL_DEBUG:
340 ssh_params->debug = SSH_LOG_DEBUG;
341 break;
342 case LOG_LEVEL_INFO:
343 ssh_params->debug = SSH_LOG_INFO;
344 break;
345 default:
346 ssh_params->debug = SSH_LOG_WARN;
351 * Editor modelines - https://www.wireshark.org/tools/modelines.html
353 * Local variables:
354 * c-basic-offset: 8
355 * tab-width: 8
356 * indent-tabs-mode: t
357 * End:
359 * vi: set shiftwidth=8 tabstop=8 noexpandtab:
360 * :indentSize=8:tabSize=8:noTabs=false: