aboutsummaryrefslogtreecommitdiff
path: root/src/config.c
diff options
context:
space:
mode:
authorJakub Jelen <jjelen@redhat.com>2018-12-03 16:02:17 +0100
committerAndreas Schneider <asn@cryptomilk.org>2018-12-10 17:56:05 +0100
commit990794c58045d5afe69d2fa861fe32b7f547b1fd (patch)
tree38e940aa30553b09c63d2b2123d6f0aed511a763 /src/config.c
parent9b1852f7281e9ccf4e06fe855d42aa6db5ad6577 (diff)
downloadlibssh-990794c58045d5afe69d2fa861fe32b7f547b1fd.tar.gz
libssh-990794c58045d5afe69d2fa861fe32b7f547b1fd.tar.xz
libssh-990794c58045d5afe69d2fa861fe32b7f547b1fd.zip
config: Parse ProxyJump configuration option and implement it using ProxyCommand with OpenSSH
Signed-off-by: Jakub Jelen <jjelen@redhat.com> Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
Diffstat (limited to 'src/config.c')
-rw-r--r--src/config.c226
1 files changed, 223 insertions, 3 deletions
diff --git a/src/config.c b/src/config.c
index 121d042f..f31665aa 100644
--- a/src/config.c
+++ b/src/config.c
@@ -106,7 +106,7 @@ static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = {
{ "numberofpasswordprompts", SOC_UNSUPPORTED},
{ "pkcs11provider", SOC_UNSUPPORTED},
{ "preferredauthentications", SOC_UNSUPPORTED},
- { "proxyjump", SOC_UNSUPPORTED},
+ { "proxyjump", SOC_PROXYJUMP},
{ "proxyusefdpass", SOC_UNSUPPORTED},
{ "pubkeyacceptedtypes", SOC_PUBKEYACCEPTEDTYPES},
{ "rekeylimit", SOC_UNSUPPORTED},
@@ -380,6 +380,215 @@ ssh_config_match(char *value, const char *pattern, bool negate)
return result;
}
+/* @brief Parse SSH URI in format [user@]host[:port] from the given string
+ *
+ * @param[in] tok String to parse
+ * @param[out] username Pointer to the location, where the new username will
+ * be stored or NULL if we do not care about the result.
+ * @param[out] hostname Pointer to the location, where the new hostname will
+ * be stored or NULL if we do not care about the result.
+ * @param[out] port Pointer to the location, where the new port will
+ * be stored or NULL if we do not care about the result.
+ *
+ * @returns SSH_OK if the provided string is in format of SSH URI,
+ * SSH_ERROR on failure
+ */
+static int
+ssh_config_parse_uri(const char *tok,
+ char **username,
+ char **hostname,
+ char **port)
+{
+ char *endp = NULL;
+ long port_n;
+
+ /* Sanitize inputs */
+ if (username != NULL) {
+ *username = NULL;
+ }
+ if (hostname != NULL) {
+ *hostname = NULL;
+ }
+ if (port != NULL) {
+ *port = NULL;
+ }
+
+ /* Username part (optional) */
+ endp = strchr(tok, '@');
+ if (endp != NULL) {
+ /* Zero-length username is not valid */
+ if (tok == endp) {
+ goto error;
+ }
+ if (username != NULL) {
+ *username = strndup(tok, endp - tok);
+ if (*username == NULL) {
+ goto error;
+ }
+ }
+ tok = endp + 1;
+ /* If there is second @ character, this does not look like our URI */
+ endp = strchr(tok, '@');
+ if (endp != NULL) {
+ goto error;
+ }
+ }
+
+ /* Hostname */
+ if (*tok == '[') {
+ /* IPv6 address is enclosed with square brackets */
+ tok++;
+ endp = strchr(tok, ']');
+ if (endp == NULL) {
+ goto error;
+ }
+ } else {
+ /* Hostnames or aliases expand to the last colon or to the end */
+ endp = strrchr(tok, ':');
+ if (endp == NULL) {
+ endp = strchr(tok, '\0');
+ }
+ }
+ if (tok == endp) {
+ /* Zero-length hostnames are not valid */
+ goto error;
+ }
+ if (hostname != NULL) {
+ *hostname = strndup(tok, endp - tok);
+ if (*hostname == NULL) {
+ goto error;
+ }
+ }
+ /* Skip also the closing bracket */
+ if (*endp == ']') {
+ endp++;
+ }
+
+ /* Port (optional) */
+ if (*endp != '\0') {
+ char *port_end = NULL;
+
+ /* Verify the port is valid positive number */
+ port_n = strtol(endp + 1, &port_end, 10);
+ if (port_n < 1 || *port_end != '\0') {
+ SSH_LOG(SSH_LOG_WARN, "Failed to parse port number."
+ " The value '%ld' is invalid or there are some"
+ " trailing characters: '%s'", port_n, port_end);
+ goto error;
+ }
+ if (port != NULL) {
+ *port = strdup(endp + 1);
+ if (*port == NULL) {
+ goto error;
+ }
+ }
+ }
+
+ return SSH_OK;
+
+error:
+ if (username != NULL) {
+ SAFE_FREE(*username);
+ }
+ if (hostname != NULL) {
+ SAFE_FREE(*hostname);
+ }
+ if (port != NULL) {
+ SAFE_FREE(*port);
+ }
+ return SSH_ERROR;
+}
+
+/* @brief: Parse the ProxyJump configuration line and if parsing,
+ * stores the result in the configuration option
+ */
+static int
+ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing)
+{
+ char *c = NULL, *cp = NULL, *endp = NULL;
+ char *username = NULL;
+ char *hostname = NULL;
+ char *port = NULL;
+ char *next = NULL;
+ int cmp, rv = SSH_ERROR;
+ bool parse_entry = do_parsing;
+
+ /* Special value none disables the proxy */
+ cmp = strcasecmp(s, "none");
+ if (cmp == 0 && do_parsing) {
+ ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, s);
+ return SSH_OK;
+ }
+
+ /* This is comma-separated list of [user@]host[:port] entries */
+ c = strdup(s);
+ if (c == NULL) {
+ ssh_set_error_oom(session);
+ return SSH_ERROR;
+ }
+
+ cp = c;
+ do {
+ endp = strchr(cp, ',');
+ if (endp != NULL) {
+ /* Split out the token */
+ *endp = '\0';
+ }
+ if (parse_entry) {
+ /* We actually care only about the first item */
+ rv = ssh_config_parse_uri(cp, &username, &hostname, &port);
+ /* The rest of the list needs to be passed on */
+ if (endp != NULL) {
+ next = strdup(endp + 1);
+ if (next == NULL) {
+ ssh_set_error_oom(session);
+ rv = SSH_ERROR;
+ }
+ }
+ } else {
+ /* The rest is just sanity-checked to avoid failures later */
+ rv = ssh_config_parse_uri(cp, NULL, NULL, NULL);
+ }
+ if (rv != SSH_OK) {
+ goto out;
+ }
+ parse_entry = 0;
+ if (endp != NULL) {
+ cp = endp + 1;
+ } else {
+ cp = NULL; /* end */
+ }
+ } while (cp != NULL);
+
+ if (hostname != NULL && do_parsing) {
+ char com[512] = {0};
+
+ rv = snprintf(com, sizeof(com), "ssh%s%s%s%s%s%s -W [%%h]:%%p %s",
+ username ? " -l " : "",
+ username ? username : "",
+ port ? " -p " : "",
+ port ? port : "",
+ next ? " -J " : "",
+ next ? next : "",
+ hostname);
+ if (rv < 0 || rv >= (int)sizeof(com)) {
+ SSH_LOG(SSH_LOG_WARN, "Too long ProxyJump configuration line");
+ rv = SSH_ERROR;
+ goto out;
+ }
+ ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, com);
+ }
+ rv = SSH_OK;
+
+out:
+ SAFE_FREE(username);
+ SAFE_FREE(hostname);
+ SAFE_FREE(port);
+ SAFE_FREE(next);
+ SAFE_FREE(c);
+ return rv;
+}
+
static int
ssh_config_parse_line(ssh_session session,
const char *line,
@@ -392,7 +601,7 @@ ssh_config_parse_line(ssh_session session,
char *keyword;
char *lowerhost;
size_t len;
- int i;
+ int i, rv;
uint8_t *seen = session->opts.options_seen;
long l;
@@ -669,10 +878,21 @@ ssh_config_parse_line(ssh_session session,
break;
case SOC_PROXYCOMMAND:
p = ssh_config_get_cmd(&s);
- if (p && *parsing) {
+ /* We share the seen value with the ProxyJump */
+ if (p && *parsing && !seen[SOC_PROXYJUMP]) {
ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, p);
}
break;
+ case SOC_PROXYJUMP:
+ p = ssh_config_get_str_tok(&s, NULL);
+ /* We share the seen value with the ProxyCommand */
+ rv = ssh_config_parse_proxy_jump(session, p,
+ (*parsing && !seen[SOC_PROXYCOMMAND]));
+ if (rv != SSH_OK) {
+ SAFE_FREE(x);
+ return -1;
+ }
+ break;
case SOC_GSSAPISERVERIDENTITY:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {