/* session-func.c -- Functions for working with SSH session. * * Copyright (C) 2013 Artyom V. Poptsov * * This file is part of Guile-SSH. * * Guile-SSH 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 3 of the * License, or (at your option) any later version. * * Guile-SSH 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 Guile-SSH. If not, see . */ #include #include #include #include #include #include "common.h" #include "error.h" #include "session-type.h" #include "key-type.h" #include "message-type.h" #include "log.h" /* SSH option mapping. */ struct option { char* symbol; int type; }; /* Guile SSH specific options that are aimed to unificate the way of session configuration. */ enum gssh_session_options { /* Should not intersect with options from SSH session API. */ GSSH_OPTIONS_CALLBACKS = 100 }; /* SSH options mapping to Guile symbols. */ static struct symbol_mapping session_options[] = { { "host", SSH_OPTIONS_HOST }, { "port", SSH_OPTIONS_PORT }, { "fd", SSH_OPTIONS_FD }, { "bindaddr", SSH_OPTIONS_BINDADDR }, { "user", SSH_OPTIONS_USER }, { "ssh-dir", SSH_OPTIONS_SSH_DIR }, { "identity", SSH_OPTIONS_IDENTITY }, { "knownhosts", SSH_OPTIONS_KNOWNHOSTS }, { "timeout", SSH_OPTIONS_TIMEOUT }, { "timeout-usec", SSH_OPTIONS_TIMEOUT_USEC }, { "ssh1", SSH_OPTIONS_SSH1 }, { "ssh2", SSH_OPTIONS_SSH2 }, { "log-verbosity", SSH_OPTIONS_LOG_VERBOSITY }, { "ciphers-c-s", SSH_OPTIONS_CIPHERS_C_S }, { "ciphers-s-c", SSH_OPTIONS_CIPHERS_S_C }, { "compression-c-s", SSH_OPTIONS_COMPRESSION_C_S }, { "compression-s-c", SSH_OPTIONS_COMPRESSION_S_C }, { "proxycommand", SSH_OPTIONS_PROXYCOMMAND }, { "stricthostkeycheck", SSH_OPTIONS_STRICTHOSTKEYCHECK }, { "compression", SSH_OPTIONS_COMPRESSION }, { "compression-level", SSH_OPTIONS_COMPRESSION_LEVEL }, { "callbacks", GSSH_OPTIONS_CALLBACKS }, { NULL, -1 } }; /* Blocking flush of the outgoing buffer. Return on of the following symbols: 'ok, 'error. 'again. Asserts: - Return value of `ssh_blocking_flush' is one of the valid constants described in libssh.h */ SCM_DEFINE (guile_ssh_blocking_flush, "blocking-flush!", 2, 0, 0, (SCM session_smob, SCM timeout), "\ Blocking flush of the outgoing buffer.\n\ Return on of the following symbols: 'ok, 'error, 'again.\ ") #define FUNC_NAME s_guile_ssh_blocking_flush { struct session_data *data = _scm_to_session_data (session_smob); int c_timeout; /* Timeout */ int res; /* Result of a function call. */ /* Check types */ SCM_ASSERT (scm_is_integer (timeout), timeout, SCM_ARG2, FUNC_NAME); c_timeout = scm_to_int (timeout); res = ssh_blocking_flush (data->ssh_session, c_timeout); switch (res) { case SSH_OK: return scm_from_locale_symbol ("ok"); case SSH_AGAIN: return scm_from_locale_symbol ("again"); case SSH_ERROR: return scm_from_locale_symbol ("error"); default: /* Must not happen. */ assert (0); return SCM_BOOL_F; } } #undef FUNC_NAME /* Set SSH session options */ /* Convert VALUE to a string and pass it to ssh_options_set */ static inline int set_string_opt (ssh_session session, int type, SCM value) { char *str; int ret; SCM_ASSERT (scm_is_string (value), value, SCM_ARG3, "session-set!"); str = scm_to_locale_string (value); ret = ssh_options_set (session, type, str); free (str); return ret; } /* Convert VALUE to uint64 and pass it to ssh_options_set */ static inline int set_uint64_opt (ssh_session session, int type, SCM value) { uint64_t c_value; SCM_ASSERT (scm_is_unsigned_integer (value, 0, UINT64_MAX), value, SCM_ARG3, "session-set!"); c_value = scm_to_uint64 (value); return ssh_options_set (session, type, &c_value); } /* Convert VALUE to uint32 and pass it to ssh_options_set */ static inline int set_uint32_opt (ssh_session session, int type, SCM value) { unsigned int c_value; SCM_ASSERT (scm_is_unsigned_integer (value, 0, UINT32_MAX), value, SCM_ARG3, "session-set!"); c_value = scm_to_uint32 (value); return ssh_options_set (session, type, &c_value); } /* Convert VALUE to int32 and pass it to ssh_options_set */ static inline int set_int32_opt (ssh_session session, int type, SCM value) { int32_t c_value; SCM_ASSERT (scm_is_integer (value), value, SCM_ARG3, "session-set!"); c_value = scm_to_int (value); return ssh_options_set (session, type, &c_value); } /* Convert VALUE to integer that represents a boolan value (0 considered as false, any other value is true), and pass it to ssh_options_set */ static inline int set_bool_opt (ssh_session session, int type, SCM value) { int32_t bool; SCM_ASSERT (scm_is_bool (value), value, SCM_ARG3, "session-set!"); bool = scm_to_bool (value); return ssh_options_set (session, type, &bool); } /* Convert VALUE to a socket file descriptor and pass it to ssh_options_set */ static inline int set_port_opt (ssh_session session, int type, SCM value) { socket_t sfd; /* Socket File Descriptor */ SCM_ASSERT (scm_port_p (value), value, SCM_ARG3, "session-set!"); sfd = scm_to_int (scm_fileno (value)); return ssh_options_set (session, type, &sfd); } /* Convert Scheme symbol to libssh constant and set the corresponding option to the value of the constant. */ static inline int set_sym_opt (ssh_session session, int type, struct symbol_mapping *sm, SCM value) { const struct symbol_mapping *opt = _scm_to_ssh_const (sm, value); if (! opt) guile_ssh_error1 ("session-set!", "Wrong value", value); return ssh_options_set (session, type, &opt->value); } /* Callbacks. */ /* Predicate. Check if a callback NAME is present in CALLBACKS alist; return 1 if it is, 0 otherwise. */ static inline int callback_set_p (SCM callbacks, const char* name) { return scm_is_true (scm_assoc (scm_from_locale_symbol (name), callbacks)); } /* Get an element NAME of the callbacks alist from a session data SD. */ static inline SCM callbacks_ref (const struct session_data *sd, const char* name) { return scm_assoc_ref (sd->callbacks, scm_from_locale_symbol (name)); } /* The callback procedure that meant to be called by libssh; the procedure in turn calls a specified Scheme procedure. USERDATA is a Guile-SSH session instance. */ static void libssh_global_request_callback (ssh_session session, ssh_message message, void *userdata) { SCM scm_session = (SCM) userdata; struct session_data *sd = _scm_to_session_data (scm_session); SCM scm_callback = callbacks_ref (sd, "global-request-callback"); SCM scm_userdata = callbacks_ref (sd, "user-data"); SCM scm_message = _scm_from_ssh_message (message, scm_session); scm_call_3 (scm_callback, scm_session, scm_message, scm_userdata); } /* The callback procedure that meant to be called by libssh to indicate the percentage of connection steps completed. The percentage is passed as STATUS. USERDATA is a Guile-SSH session instance. */ static void libssh_connect_status_callback (void *userdata, float status) { SCM scm_session = (SCM) userdata; struct session_data *sd = _scm_to_session_data (scm_session); SCM scm_callback = callbacks_ref (sd, "connect-status-callback"); SCM scm_userdata = callbacks_ref (sd, "user-data"); scm_call_3 (scm_callback, scm_session, scm_from_double (status), scm_userdata); } /* Predicate. Return 1 if X is a Scheme procedure, 0 otherwise. */ static inline int scm_is_procedure (SCM x) { return scm_to_bool (scm_procedure_p (x)); } /* Validate callback NAME. Throw 'guile-ssh-error' exception on an error. */ static void validate_callback (SCM session, const struct session_data *sd, const char* name) { if (! scm_is_procedure (callbacks_ref (sd, name))) { enum { BUFSZ = 70 }; char msg[BUFSZ]; snprintf (msg, BUFSZ, "'%s' must be a procedure", name); guile_ssh_error1 ("session-set!", msg, scm_list_2 (session, sd->callbacks)); } } /* Set libssh callbacks for a SESSION. The procedure expects CALLBACKS to be an alist object. Return SSH_OK if callbacks were set succesfully, SSH_ERROR otherwise. */ static int set_callbacks (SCM session, struct session_data *sd, SCM callbacks) { struct ssh_callbacks_struct *cb = (struct ssh_callbacks_struct *) scm_gc_malloc (sizeof (struct ssh_callbacks_struct), "ssh-callbacks"); SCM_ASSERT (scm_to_bool (scm_list_p (callbacks)), callbacks, SCM_ARG3, "session-set!"); sd->callbacks = callbacks; cb->userdata = session; if (callback_set_p (callbacks, "global-request-callback")) { validate_callback (session, sd, "global-request-callback"); cb->global_request_function = libssh_global_request_callback; } if (callback_set_p (callbacks, "connect-status-callback")) { validate_callback (session, sd, "connect-status-callback"); cb->connect_status_function = libssh_connect_status_callback; } ssh_callbacks_init (cb); scm_remember_upto_here_2 (session, callbacks); return ssh_set_callbacks (sd->ssh_session, cb); } /* Set an SSH session option. */ static int set_option (SCM scm_session, struct session_data* sd, int type, SCM value) { ssh_session session = sd->ssh_session; switch (type) { case SSH_OPTIONS_PORT: return set_uint32_opt (session, type, value); case SSH_OPTIONS_HOST: case SSH_OPTIONS_BINDADDR: case SSH_OPTIONS_USER: case SSH_OPTIONS_COMPRESSION: case SSH_OPTIONS_SSH_DIR: case SSH_OPTIONS_KNOWNHOSTS: case SSH_OPTIONS_IDENTITY: case SSH_OPTIONS_CIPHERS_C_S: case SSH_OPTIONS_CIPHERS_S_C: case SSH_OPTIONS_COMPRESSION_C_S: case SSH_OPTIONS_COMPRESSION_S_C: case SSH_OPTIONS_PROXYCOMMAND: return set_string_opt (session, type, value); case SSH_OPTIONS_LOG_VERBOSITY: return set_sym_opt (session, type, log_verbosity, value); case SSH_OPTIONS_COMPRESSION_LEVEL: return set_int32_opt (session, type, value); case SSH_OPTIONS_TIMEOUT: case SSH_OPTIONS_TIMEOUT_USEC: return set_uint64_opt (session, type, value); case SSH_OPTIONS_SSH1: case SSH_OPTIONS_SSH2: case SSH_OPTIONS_STRICTHOSTKEYCHECK: return set_bool_opt (session, type, value); case SSH_OPTIONS_FD: return set_port_opt (session, type, value); case GSSH_OPTIONS_CALLBACKS: return set_callbacks (scm_session, sd, value); default: guile_ssh_error1 ("session-set!", "Operation is not supported yet: %a~%", scm_from_int (type)); } return -1; /* ERROR */ } /* Set a SSH option. Return #t on success, #f on error. */ SCM_DEFINE (guile_ssh_session_set, "session-set!", 3, 0, 0, (SCM session, SCM option, SCM value), "\ Set a SSH option OPTION. Throw an guile-ssh-error on error.\n\ Return value is undefined.\ ") #define FUNC_NAME s_guile_ssh_session_set { struct session_data* data = _scm_to_session_data (session); const struct symbol_mapping *opt; /* Session option */ int res; /* Result of a function call */ SCM_ASSERT (scm_is_symbol (option), option, SCM_ARG2, FUNC_NAME); opt = _scm_to_ssh_const (session_options, option); if(! opt) guile_ssh_error1 (FUNC_NAME, "No such option", option); res = set_option (session, data, opt->value, value); if (res != SSH_OK) { guile_ssh_error1 (FUNC_NAME, "Unable to set the option", scm_list_2 (option, value)); } scm_remember_upto_here_1 (session); return SCM_UNDEFINED; } #undef FUNC_NAME /* Options whose values can be requested through `session-get' */ static struct symbol_mapping session_options_getable[] = { { "host", SSH_OPTIONS_HOST }, { "port", SSH_OPTIONS_PORT }, { "user", SSH_OPTIONS_USER }, { "identity", SSH_OPTIONS_IDENTITY }, { "proxycommand", SSH_OPTIONS_PROXYCOMMAND }, { "callbacks", GSSH_OPTIONS_CALLBACKS }, { NULL, -1 } }; SCM_DEFINE (guile_ssh_session_get, "session-get", 2, 0, 0, (SCM session, SCM option), "\ Get value of the OPTION. Throw `guile-ssh-error' on an error.\ ") #define FUNC_NAME s_guile_ssh_session_get { struct session_data*sd = _scm_to_session_data (session); const struct symbol_mapping *opt = NULL; SCM value = SCM_UNDEFINED; /*Value of the option */ int res = SSH_OK; SCM_ASSERT (scm_is_symbol (option), option, SCM_ARG2, FUNC_NAME); opt = _scm_to_ssh_const (session_options_getable, option); if (! opt) guile_ssh_error1 (FUNC_NAME, "Wrong option", option); if (opt->value == SSH_OPTIONS_PORT) { unsigned int port; res = ssh_options_get_port (sd->ssh_session, &port); value = (res == SSH_OK) ? scm_from_int (port) : SCM_UNDEFINED; } else if (opt->value == GSSH_OPTIONS_CALLBACKS) { value = sd->callbacks; } else { char *c_value = NULL; res = ssh_options_get (sd->ssh_session, opt->value, &c_value); value = (res == SSH_OK) ? scm_from_locale_string (c_value) : SCM_UNDEFINED; } if (res == SSH_ERROR) guile_ssh_error1 (FUNC_NAME, "Unable to get value of the option", option); return value; } #undef FUNC_NAME /* Asserts: - SESSION is a Guile-SSH session object. - FILE_NAME either a string or '#f' */ SCM_GSSH_DEFINE (gssh_session_parse_config, "%gssh-session-parse-config!", 2, (SCM session, SCM file_name)) #define FUNC_NAME s_gssh_session_parse_config { struct session_data *sd = _scm_to_session_data (session); int res; char* c_file_name = NULL; SCM_ASSERT (scm_is_string (file_name) || (scm_is_bool (file_name) && scm_is_false (file_name)), file_name, SCM_ARG2, FUNC_NAME); scm_dynwind_begin (0); if (scm_is_string (file_name)) { c_file_name = scm_to_locale_string (file_name); scm_dynwind_free (c_file_name); } res = ssh_options_parse_config (sd->ssh_session, /* 'NULL' means that we should read the default '~/.ssh/config' file. */ c_file_name); if (res != SSH_OK) { guile_ssh_error1 (FUNC_NAME, "Could not read the configuration file", scm_list_2 (session, file_name)); } scm_dynwind_end (); return SCM_UNDEFINED; } #undef FUNC_NAME /* Connect to the SSH server. Return one of the following symbols: 'ok, 'again, 'error Asserts: - Return value of `ssh_connect' is one of the valid constants described in libssh.h */ SCM_DEFINE (guile_ssh_connect_x, "connect!", 1, 0, 0, (SCM session), "\ Connect to the SSH server.\n\ Return one of the following symbols: 'ok, 'again, 'error\ ") #define FUNC_NAME s_guile_ssh_connect_x { struct session_data* data = _scm_to_session_data (session); int res = ssh_connect (data->ssh_session); switch (res) { case SSH_OK: return scm_from_locale_symbol ("ok"); case SSH_AGAIN: return scm_from_locale_symbol ("again"); case SSH_ERROR: return scm_from_locale_symbol ("error"); default: /* Must not happen */ assert (0); return SCM_BOOL_F; } } #undef FUNC_NAME SCM_DEFINE (guile_ssh_disconnect, "disconnect!", 1, 0, 0, (SCM arg1), "\ Disconnect from a session (client or server).\n\ Return value is undefined.\ ") { struct session_data* session_data = _scm_to_session_data (arg1); ssh_disconnect (session_data->ssh_session); return SCM_UNDEFINED; } SCM_DEFINE (guile_ssh_get_protocol_version, "get-protocol-version", 1, 0, 0, (SCM arg1), "\ Get SSH version.\n\ Return 1 for SSH1, 2 for SSH2 or #f on error.\ ") #define FUNC_NAME s_guile_ssh_get_protocol_version { struct session_data* data = _scm_to_session_data (arg1); SCM ret; int version; GSSH_VALIDATE_CONNECTED_SESSION (data, arg1, SCM_ARG1); version = ssh_get_version (data->ssh_session); if (version >= 0) ret = scm_from_int (version); else ret = SCM_BOOL_F; return ret; } #undef FUNC_NAME SCM_DEFINE (guile_ssh_get_error, "get-error", 1, 0, 1, (SCM arg1), "\ Retrieve the error text message from the last error.\ ") { struct session_data* data = _scm_to_session_data (arg1); SCM error = scm_from_locale_string (ssh_get_error (data->ssh_session)); return error; } /* Authenticate the server. Return one of the following symbols: 'ok, 'known-changed, 'found-other, 'not-known, 'file-not-found, 'error Asserts: - Return value of `ssh_is_server_known' is one of the valid constants described in libssh.h */ SCM_DEFINE (guile_ssh_authenticate_server, "authenticate-server", 1, 0, 0, (SCM session), "\ Authenticate the server.\n\ Return one of the following symbols: 'ok, 'known-changed, 'found-other,\n\ 'not-known, 'file-not-found, 'error\ ") #define FUNC_NAME s_guile_ssh_authenticate_server { struct session_data* data = _scm_to_session_data (session); int res; GSSH_VALIDATE_CONNECTED_SESSION (data, session, SCM_ARG1); res = ssh_is_server_known (data->ssh_session); switch (res) { case SSH_SERVER_KNOWN_OK: return scm_from_locale_symbol ("ok"); case SSH_SERVER_KNOWN_CHANGED: return scm_from_locale_symbol ("known-changed"); case SSH_SERVER_FOUND_OTHER: return scm_from_locale_symbol ("found-other"); case SSH_SERVER_NOT_KNOWN: return scm_from_locale_symbol ("not-known"); case SSH_SERVER_FILE_NOT_FOUND: return scm_from_locale_symbol ("file-not-found"); case SSH_SERVER_ERROR: return scm_from_locale_symbol ("error"); default: /* Must not happen. */ assert (0); return SCM_BOOL_F; } } #undef FUNC_NAME SCM_DEFINE (guile_ssh_get_server_public_key, "get-server-public-key", 1, 0, 0, (SCM session), "\ Get server public key from a SESSION.\n\ Return server's public key. Throw `guile-ssh-error' on error.\ ") #define FUNC_NAME s_guile_ssh_get_server_public_key { struct session_data *sd = _scm_to_session_data (session); struct key_data *kd; int res; SCM key_smob; GSSH_VALIDATE_CONNECTED_SESSION (sd, session, SCM_ARG1); kd = (struct key_data *) scm_gc_malloc (sizeof (struct key_data), "ssh key"); /* TODO: Check `kd' for NULL. */ res = ssh_get_publickey (sd->ssh_session, &kd->ssh_key); if (res != SSH_OK) guile_ssh_error1 (FUNC_NAME, "Unable to get the server key", session); SCM_NEWSMOB (key_smob, key_tag, kd); return key_smob; } #undef FUNC_NAME SCM_DEFINE (guile_ssh_write_known_host, "write-known-host!", 1, 0, 0, (SCM session), "\ Write the current server as known in the known hosts file.\n\ Return value is undefined.\ ") #define FUNC_NAME s_guile_ssh_write_known_host { struct session_data *session_data = _scm_to_session_data (session); int res; GSSH_VALIDATE_CONNECTED_SESSION (session_data, session, SCM_ARG1); res = ssh_write_knownhost (session_data->ssh_session); if (res != SSH_OK) guile_ssh_session_error1 (FUNC_NAME, session_data->ssh_session, session); return SCM_UNDEFINED; } #undef FUNC_NAME /* Predicates */ SCM_DEFINE (guile_ssh_is_connected_p, "connected?", 1, 0, 0, (SCM arg1), "\ Check if we are connected.\n\ Return #f if we are connected to a server, #f if we aren't.\ ") { struct session_data* data = _scm_to_session_data (arg1); int res = ssh_is_connected (data->ssh_session); return scm_from_bool (res); } /* Initialize session related functions. */ void init_session_func (void) { #include "session-func.x" } /* session-func.c ends here */