]> andersk Git - openssh.git/blobdiff - sshconnect.c
- djm@cvs.openbsd.org 2010/01/30 02:54:53
[openssh.git] / sshconnect.c
index e26efa4dc1c2d51248868a5cfb9141f80fc5373c..63c4650f738b9957903d5fdecfff5385f48337df 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshconnect.c,v 1.185 2006/06/14 10:50:42 djm Exp $ */
+/* $OpenBSD: sshconnect.c,v 1.218 2010/01/13 00:19:04 dtucker Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <sys/stat.h>
+#include <sys/socket.h>
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
 
 #include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
 #ifdef HAVE_PATHS_H
 #include <paths.h>
 #endif
+#include <pwd.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
 
-#include "ssh.h"
 #include "xmalloc.h"
+#include "key.h"
+#include "hostfile.h"
+#include "ssh.h"
 #include "rsa.h"
 #include "buffer.h"
 #include "packet.h"
@@ -39,6 +57,8 @@
 #include "atomicio.h"
 #include "misc.h"
 #include "dns.h"
+#include "roaming.h"
+#include "version.h"
 
 char *client_version_string = NULL;
 char *server_version_string = NULL;
@@ -52,10 +72,6 @@ extern uid_t original_real_uid;
 extern uid_t original_effective_uid;
 extern pid_t proxy_command_pid;
 
-#ifndef INET6_ADDRSTRLEN               /* for non IPv6 machines */
-#define INET6_ADDRSTRLEN 46
-#endif
-
 static int show_other_keys(const char *, Key *);
 static void warn_changed_key(Key *);
 
@@ -68,7 +84,10 @@ ssh_proxy_connect(const char *host, u_short port, const char *proxy_command)
        char *command_string, *tmp;
        int pin[2], pout[2];
        pid_t pid;
-       char strport[NI_MAXSERV];
+       char *shell, strport[NI_MAXSERV];
+
+       if ((shell = getenv("SHELL")) == NULL)
+               shell = _PATH_BSHELL;
 
        /* Convert the port number into a string. */
        snprintf(strport, sizeof strport, "%hu", port);
@@ -114,7 +133,7 @@ ssh_proxy_connect(const char *host, u_short port, const char *proxy_command)
 
                /* Stderr is left as it is so that error messages get
                   printed on the user's terminal. */
-               argv[0] = _PATH_BSHELL;
+               argv[0] = shell;
                argv[1] = "-c";
                argv[2] = command_string;
                argv[3] = NULL;
@@ -140,6 +159,8 @@ ssh_proxy_connect(const char *host, u_short port, const char *proxy_command)
 
        /* Set the connection file descriptors. */
        packet_set_connection(pout[0], pin[1]);
+       packet_set_timeout(options.server_alive_interval,
+           options.server_alive_count_max);
 
        /* Indicate OK return */
        return 0;
@@ -171,8 +192,11 @@ ssh_create_socket(int privileged, struct addrinfo *ai)
                return sock;
        }
        sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
-       if (sock < 0)
+       if (sock < 0) {
                error("socket: %.100s", strerror(errno));
+               return -1;
+       }
+       fcntl(sock, F_SETFD, FD_CLOEXEC);
 
        /* Bind the socket to an alternative local IP address */
        if (options.bind_address == NULL)
@@ -183,10 +207,10 @@ ssh_create_socket(int privileged, struct addrinfo *ai)
        hints.ai_socktype = ai->ai_socktype;
        hints.ai_protocol = ai->ai_protocol;
        hints.ai_flags = AI_PASSIVE;
-       gaierr = getaddrinfo(options.bind_address, "0", &hints, &res);
+       gaierr = getaddrinfo(options.bind_address, NULL, &hints, &res);
        if (gaierr) {
                error("getaddrinfo: %s: %s", options.bind_address,
-                   gai_strerror(gaierr));
+                   ssh_gai_strerror(gaierr));
                close(sock);
                return -1;
        }
@@ -202,30 +226,36 @@ ssh_create_socket(int privileged, struct addrinfo *ai)
 
 static int
 timeout_connect(int sockfd, const struct sockaddr *serv_addr,
-    socklen_t addrlen, int timeout)
+    socklen_t addrlen, int *timeoutp)
 {
        fd_set *fdset;
-       struct timeval tv;
+       struct timeval tv, t_start;
        socklen_t optlen;
        int optval, rc, result = -1;
 
-       if (timeout <= 0)
-               return (connect(sockfd, serv_addr, addrlen));
+       gettimeofday(&t_start, NULL);
+
+       if (*timeoutp <= 0) {
+               result = connect(sockfd, serv_addr, addrlen);
+               goto done;
+       }
 
        set_nonblock(sockfd);
        rc = connect(sockfd, serv_addr, addrlen);
        if (rc == 0) {
                unset_nonblock(sockfd);
-               return (0);
+               result = 0;
+               goto done;
+       }
+       if (errno != EINPROGRESS) {
+               result = -1;
+               goto done;
        }
-       if (errno != EINPROGRESS)
-               return (-1);
 
        fdset = (fd_set *)xcalloc(howmany(sockfd + 1, NFDBITS),
            sizeof(fd_mask));
        FD_SET(sockfd, fdset);
-       tv.tv_sec = timeout;
-       tv.tv_usec = 0;
+       ms_to_timeval(&tv, *timeoutp);
 
        for (;;) {
                rc = select(sockfd + 1, NULL, fdset, NULL, &tv);
@@ -264,6 +294,16 @@ timeout_connect(int sockfd, const struct sockaddr *serv_addr,
        }
 
        xfree(fdset);
+
+ done:
+       if (result == 0 && *timeoutp > 0) {
+               ms_subtract_diff(&t_start, timeoutp);
+               if (*timeoutp <= 0) {
+                       errno = ETIMEDOUT;
+                       result = -1;
+               }
+       }
+
        return (result);
 }
 
@@ -280,8 +320,8 @@ timeout_connect(int sockfd, const struct sockaddr *serv_addr,
  */
 int
 ssh_connect(const char *host, struct sockaddr_storage * hostaddr,
-    u_short port, int family, int connection_attempts,
-    int needpriv, const char *proxy_command)
+    u_short port, int family, int connection_attempts, int *timeout_ms,
+    int want_keepalive, int needpriv, const char *proxy_command)
 {
        int gaierr;
        int on = 1;
@@ -302,13 +342,15 @@ ssh_connect(const char *host, struct sockaddr_storage * hostaddr,
        hints.ai_socktype = SOCK_STREAM;
        snprintf(strport, sizeof strport, "%u", port);
        if ((gaierr = getaddrinfo(host, strport, &hints, &aitop)) != 0)
-               fatal("%s: %.100s: %s", __progname, host,
-                   gai_strerror(gaierr));
+               fatal("%s: Could not resolve hostname %.100s: %s", __progname,
+                   host, ssh_gai_strerror(gaierr));
 
        for (attempt = 0; attempt < connection_attempts; attempt++) {
-               if (attempt > 0)
+               if (attempt > 0) {
+                       /* Sleep a moment before retrying. */
+                       sleep(1);
                        debug("Trying again...");
-
+               }
                /*
                 * Loop through addresses for this host, and try each one in
                 * sequence until the connection succeeds.
@@ -332,7 +374,7 @@ ssh_connect(const char *host, struct sockaddr_storage * hostaddr,
                                continue;
 
                        if (timeout_connect(sock, ai->ai_addr, ai->ai_addrlen,
-                           options.connection_timeout) >= 0) {
+                           timeout_ms) >= 0) {
                                /* Successful connection. */
                                memcpy(hostaddr, ai->ai_addr, ai->ai_addrlen);
                                break;
@@ -345,9 +387,6 @@ ssh_connect(const char *host, struct sockaddr_storage * hostaddr,
                }
                if (sock != -1)
                        break;  /* Successful connection. */
-
-               /* Sleep a moment before retrying. */
-               sleep(1);
        }
 
        freeaddrinfo(aitop);
@@ -362,13 +401,15 @@ ssh_connect(const char *host, struct sockaddr_storage * hostaddr,
        debug("Connection established.");
 
        /* Set SO_KEEPALIVE if requested. */
-       if (options.tcp_keep_alive &&
+       if (want_keepalive &&
            setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (void *)&on,
            sizeof(on)) < 0)
                error("setsockopt SO_KEEPALIVE: %.100s", strerror(errno));
 
        /* Set the connection. */
        packet_set_connection(sock, sock);
+       packet_set_timeout(options.server_alive_interval,
+           options.server_alive_count_max);
 
        return 0;
 }
@@ -377,8 +418,8 @@ ssh_connect(const char *host, struct sockaddr_storage * hostaddr,
  * Waits for the server identification string, and sends our own
  * identification string.
  */
-static void
-ssh_exchange_identification(void)
+void
+ssh_exchange_identification(int timeout_ms)
 {
        char buf[256], remote_version[256];     /* must be same size! */
        int remote_major, remote_minor, mismatch;
@@ -386,16 +427,44 @@ ssh_exchange_identification(void)
        int connection_out = packet_get_connection_out();
        int minor1 = PROTOCOL_MINOR_1;
        u_int i, n;
+       size_t len;
+       int fdsetsz, remaining, rc;
+       struct timeval t_start, t_remaining;
+       fd_set *fdset;
+
+       fdsetsz = howmany(connection_in + 1, NFDBITS) * sizeof(fd_mask);
+       fdset = xcalloc(1, fdsetsz);
 
        /* Read other side's version identification. */
+       remaining = timeout_ms;
        for (n = 0;;) {
                for (i = 0; i < sizeof(buf) - 1; i++) {
-                       size_t len = atomicio(read, connection_in, &buf[i], 1);
+                       if (timeout_ms > 0) {
+                               gettimeofday(&t_start, NULL);
+                               ms_to_timeval(&t_remaining, remaining);
+                               FD_SET(connection_in, fdset);
+                               rc = select(connection_in + 1, fdset, NULL,
+                                   fdset, &t_remaining);
+                               ms_subtract_diff(&t_start, &remaining);
+                               if (rc == 0 || remaining <= 0)
+                                       fatal("Connection timed out during "
+                                           "banner exchange");
+                               if (rc == -1) {
+                                       if (errno == EINTR)
+                                               continue;
+                                       fatal("ssh_exchange_identification: "
+                                           "select: %s", strerror(errno));
+                               }
+                       }
+
+                       len = roaming_atomicio(read, connection_in, &buf[i], 1);
 
                        if (len != 1 && errno == EPIPE)
-                               fatal("ssh_exchange_identification: Connection closed by remote host");
+                               fatal("ssh_exchange_identification: "
+                                   "Connection closed by remote host");
                        else if (len != 1)
-                               fatal("ssh_exchange_identification: read: %.100s", strerror(errno));
+                               fatal("ssh_exchange_identification: "
+                                   "read: %.100s", strerror(errno));
                        if (buf[i] == '\r') {
                                buf[i] = '\n';
                                buf[i + 1] = 0;
@@ -406,7 +475,8 @@ ssh_exchange_identification(void)
                                break;
                        }
                        if (++n > 65536)
-                               fatal("ssh_exchange_identification: No banner received");
+                               fatal("ssh_exchange_identification: "
+                                   "No banner received");
                }
                buf[sizeof(buf) - 1] = 0;
                if (strncmp(buf, "SSH-", 4) == 0)
@@ -414,6 +484,7 @@ ssh_exchange_identification(void)
                debug("ssh_exchange_identification: %s", buf);
        }
        server_version_string = xstrdup(buf);
+       xfree(fdset);
 
        /*
         * Check that the versions match.  In future this might accept
@@ -467,11 +538,12 @@ ssh_exchange_identification(void)
                    (options.protocol & SSH_PROTO_2) ? PROTOCOL_MAJOR_2 : PROTOCOL_MAJOR_1,
                    remote_major);
        /* Send our own protocol version identification. */
-       snprintf(buf, sizeof buf, "SSH-%d.%d-%.100s\n",
+       snprintf(buf, sizeof buf, "SSH-%d.%d-%.100s%s",
            compat20 ? PROTOCOL_MAJOR_2 : PROTOCOL_MAJOR_1,
            compat20 ? PROTOCOL_MINOR_2 : minor1,
-           SSH_VERSION);
-       if (atomicio(vwrite, connection_out, buf, strlen(buf)) != strlen(buf))
+           SSH_VERSION, compat20 ? "\r\n" : "\n");
+       if (roaming_atomicio(vwrite, connection_out, buf, strlen(buf))
+           != strlen(buf))
                fatal("write: %.100s", strerror(errno));
        client_version_string = xstrdup(buf);
        chop(client_version_string);
@@ -508,21 +580,25 @@ confirm(const char *prompt)
  * check whether the supplied host key is valid, return -1 if the key
  * is not valid. the user_hostfile will not be updated if 'readonly' is true.
  */
+#define RDRW   0
+#define RDONLY 1
+#define ROQUIET        2
 static int
-check_host_key(char *host, struct sockaddr *hostaddr, Key *host_key,
-    int readonly, const char *user_hostfile, const char *system_hostfile)
+check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port,
+    Key *host_key, int readonly, const char *user_hostfile,
+    const char *system_hostfile)
 {
        Key *file_key;
        const char *type = key_type(host_key);
-       char *ip = NULL;
-       char hostline[1000], *hostp, *fp;
+       char *ip = NULL, *host = NULL;
+       char hostline[1000], *hostp, *fp, *ra;
        HostStatus host_status;
        HostStatus ip_status;
        int r, local = 0, host_ip_differ = 0;
        int salen;
        char ntop[NI_MAXHOST];
        char msg[1024];
-       int len, host_line, ip_line;
+       int len, host_line, ip_line, cancelled_forwarding = 0;
        const char *host_file = NULL, *ip_file = NULL;
 
        /*
@@ -565,26 +641,30 @@ check_host_key(char *host, struct sockaddr *hostaddr, Key *host_key,
                if (getnameinfo(hostaddr, salen, ntop, sizeof(ntop),
                    NULL, 0, NI_NUMERICHOST) != 0)
                        fatal("check_host_key: getnameinfo failed");
-               ip = xstrdup(ntop);
+               ip = put_host_port(ntop, port);
        } else {
                ip = xstrdup("<no hostip for proxy command>");
        }
+
        /*
         * Turn off check_host_ip if the connection is to localhost, via proxy
         * command or if we don't have a hostname to compare with
         */
-       if (options.check_host_ip &&
-           (local || strcmp(host, ip) == 0 || options.proxy_command != NULL))
+       if (options.check_host_ip && (local ||
+           strcmp(hostname, ip) == 0 || options.proxy_command != NULL))
                options.check_host_ip = 0;
 
        /*
-        * Allow the user to record the key under a different name. This is
-        * useful for ssh tunneling over forwarded connections or if you run
-        * multiple sshd's on different ports on the same machine.
+        * Allow the user to record the key under a different name or
+        * differentiate a non-standard port.  This is useful for ssh
+        * tunneling over forwarded connections or if you run multiple
+        * sshd's on different ports on the same machine.
         */
        if (options.host_key_alias != NULL) {
-               host = options.host_key_alias;
+               host = xstrdup(options.host_key_alias);
                debug("using hostkeyalias: %s", host);
+       } else {
+               host = put_host_port(hostname, port);
        }
 
        /*
@@ -650,9 +730,25 @@ check_host_key(char *host, struct sockaddr *hostaddr, Key *host_key,
                                logit("Warning: Permanently added the %s host "
                                    "key for IP address '%.128s' to the list "
                                    "of known hosts.", type, ip);
+               } else if (options.visual_host_key) {
+                       fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX);
+                       ra = key_fingerprint(host_key, SSH_FP_MD5,
+                           SSH_FP_RANDOMART);
+                       logit("Host key fingerprint is %s\n%s\n", fp, ra);
+                       xfree(ra);
+                       xfree(fp);
                }
                break;
        case HOST_NEW:
+               if (options.host_key_alias == NULL && port != 0 &&
+                   port != SSH_DEFAULT_PORT) {
+                       debug("checking without port identifier");
+                       if (check_host_key(hostname, hostaddr, 0, host_key,
+                           ROQUIET, user_hostfile, system_hostfile) == 0) {
+                               debug("found matching key w/out port");
+                               break;
+                       }
+               }
                if (readonly)
                        goto fail;
                /* The host is new. */
@@ -676,6 +772,8 @@ check_host_key(char *host, struct sockaddr *hostaddr, Key *host_key,
                                snprintf(msg1, sizeof(msg1), ".");
                        /* The default */
                        fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX);
+                       ra = key_fingerprint(host_key, SSH_FP_MD5,
+                           SSH_FP_RANDOMART);
                        msg2[0] = '\0';
                        if (options.verify_host_key_dns) {
                                if (matching_host_key_dns)
@@ -690,10 +788,14 @@ check_host_key(char *host, struct sockaddr *hostaddr, Key *host_key,
                        snprintf(msg, sizeof(msg),
                            "The authenticity of host '%.200s (%s)' can't be "
                            "established%s\n"
-                           "%s key fingerprint is %s.\n%s"
+                           "%s key fingerprint is %s.%s%s\n%s"
                            "Are you sure you want to continue connecting "
                            "(yes/no)? ",
-                           host, ip, msg1, type, fp, msg2);
+                           host, ip, msg1, type, fp,
+                           options.visual_host_key ? "\n" : "",
+                           options.visual_host_key ? ra : "",
+                           msg2);
+                       xfree(ra);
                        xfree(fp);
                        if (!confirm(msg))
                                goto fail;
@@ -732,6 +834,8 @@ check_host_key(char *host, struct sockaddr *hostaddr, Key *host_key,
                            "list of known hosts.", hostp, type);
                break;
        case HOST_CHANGED:
+               if (readonly == ROQUIET)
+                       goto fail;
                if (options.check_host_ip && host_ip_differ) {
                        char *key_msg;
                        if (ip_status == HOST_NEW)
@@ -744,7 +848,7 @@ check_host_key(char *host, struct sockaddr *hostaddr, Key *host_key,
                        error("@       WARNING: POSSIBLE DNS SPOOFING DETECTED!          @");
                        error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
                        error("The %s host key for %s has changed,", type, host);
-                       error("and the key for the according IP address %s", ip);
+                       error("and the key for the corresponding IP address %s", ip);
                        error("%s. This could either mean that", key_msg);
                        error("DNS SPOOFING is happening or the IP address for the host");
                        error("and its host key have changed at the same time.");
@@ -770,33 +874,38 @@ check_host_key(char *host, struct sockaddr *hostaddr, Key *host_key,
                /*
                 * If strict host key checking has not been requested, allow
                 * the connection but without MITM-able authentication or
-                * agent forwarding.
+                * forwarding.
                 */
                if (options.password_authentication) {
                        error("Password authentication is disabled to avoid "
                            "man-in-the-middle attacks.");
                        options.password_authentication = 0;
+                       cancelled_forwarding = 1;
                }
                if (options.kbd_interactive_authentication) {
                        error("Keyboard-interactive authentication is disabled"
                            " to avoid man-in-the-middle attacks.");
                        options.kbd_interactive_authentication = 0;
                        options.challenge_response_authentication = 0;
+                       cancelled_forwarding = 1;
                }
                if (options.challenge_response_authentication) {
                        error("Challenge/response authentication is disabled"
                            " to avoid man-in-the-middle attacks.");
                        options.challenge_response_authentication = 0;
+                       cancelled_forwarding = 1;
                }
                if (options.forward_agent) {
                        error("Agent forwarding is disabled to avoid "
                            "man-in-the-middle attacks.");
                        options.forward_agent = 0;
+                       cancelled_forwarding = 1;
                }
                if (options.forward_x11) {
                        error("X11 forwarding is disabled to avoid "
                            "man-in-the-middle attacks.");
                        options.forward_x11 = 0;
+                       cancelled_forwarding = 1;
                }
                if (options.num_local_forwards > 0 ||
                    options.num_remote_forwards > 0) {
@@ -804,12 +913,23 @@ check_host_key(char *host, struct sockaddr *hostaddr, Key *host_key,
                            "man-in-the-middle attacks.");
                        options.num_local_forwards =
                            options.num_remote_forwards = 0;
+                       cancelled_forwarding = 1;
                }
+               if (options.tun_open != SSH_TUNMODE_NO) {
+                       error("Tunnel forwarding is disabled to avoid "
+                           "man-in-the-middle attacks.");
+                       options.tun_open = SSH_TUNMODE_NO;
+                       cancelled_forwarding = 1;
+               }
+               if (options.exit_on_forward_failure && cancelled_forwarding)
+                       fatal("Error: forwarding disabled due to host key "
+                           "check failure");
+               
                /*
                 * XXX Should permit the user to change to use the new id.
                 * This could be done by converting the host key to an
                 * identifying sentence, tell that the host identifies itself
-                * by that sentence, and ask the user if he/she whishes to
+                * by that sentence, and ask the user if he/she wishes to
                 * accept the authentication.
                 */
                break;
@@ -846,10 +966,12 @@ check_host_key(char *host, struct sockaddr *hostaddr, Key *host_key,
        }
 
        xfree(ip);
+       xfree(host);
        return 0;
 
 fail:
        xfree(ip);
+       xfree(host);
        return -1;
 }
 
@@ -883,12 +1005,13 @@ verify_host_key(char *host, struct sockaddr *hostaddr, Key *host_key)
        /* return ok if the key can be found in an old keyfile */
        if (stat(options.system_hostfile2, &st) == 0 ||
            stat(options.user_hostfile2, &st) == 0) {
-               if (check_host_key(host, hostaddr, host_key, /*readonly*/ 1,
-                   options.user_hostfile2, options.system_hostfile2) == 0)
+               if (check_host_key(host, hostaddr, options.port, host_key,
+                   RDONLY, options.user_hostfile2,
+                   options.system_hostfile2) == 0)
                        return 0;
        }
-       return check_host_key(host, hostaddr, host_key, /*readonly*/ 0,
-           options.user_hostfile, options.system_hostfile);
+       return check_host_key(host, hostaddr, options.port, host_key,
+           RDRW, options.user_hostfile, options.system_hostfile);
 }
 
 /*
@@ -900,7 +1023,7 @@ verify_host_key(char *host, struct sockaddr *hostaddr, Key *host_key)
  */
 void
 ssh_login(Sensitive *sensitive, const char *orighost,
-    struct sockaddr *hostaddr, struct passwd *pw)
+    struct sockaddr *hostaddr, struct passwd *pw, int timeout_ms)
 {
        char *host, *cp;
        char *server_user, *local_user;
@@ -915,7 +1038,7 @@ ssh_login(Sensitive *sensitive, const char *orighost,
                        *cp = (char)tolower(*cp);
 
        /* Exchange protocol version identification strings with the server. */
-       ssh_exchange_identification();
+       ssh_exchange_identification(timeout_ms);
 
        /* Put the connection into non-blocking mode. */
        packet_set_nonblocking();
@@ -954,18 +1077,20 @@ static int
 show_key_from_file(const char *file, const char *host, int keytype)
 {
        Key *found;
-       char *fp;
+       char *fp, *ra;
        int line, ret;
 
        found = key_new(keytype);
        if ((ret = lookup_key_in_hostfile_by_type(file, host,
            keytype, found, &line))) {
                fp = key_fingerprint(found, SSH_FP_MD5, SSH_FP_HEX);
+               ra = key_fingerprint(found, SSH_FP_MD5, SSH_FP_RANDOMART);
                logit("WARNING: %s key found for host %s\n"
                    "in %s:%d\n"
-                   "%s key fingerprint %s.",
+                   "%s key fingerprint %s.\n%s\n",
                    key_type(found), host, file, line,
-                   key_type(found), fp);
+                   key_type(found), fp, ra);
+               xfree(ra);
                xfree(fp);
        }
        key_free(found);
This page took 0.569603 seconds and 4 git commands to generate.