]> andersk Git - openssh.git/blobdiff - canohost.c
- djm@cvs.openbsd.org 2010/01/30 02:54:53
[openssh.git] / canohost.c
index 5890a127b2a696949776600b0d9785381d31dc93..ef94d9155ea446d8cb5604d10a06026b5cd62751 100644 (file)
+/* $OpenBSD: canohost.c,v 1.66 2010/01/13 01:20:20 dtucker Exp $ */
 /*
- * 
- * canohost.c
- * 
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
- * 
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
  *                    All rights reserved
- * 
- * Created: Sun Jul  2 17:52:22 1995 ylo
- * 
  * Functions for returning the canonical host name of the remote site.
- * 
+ *
+ * As far as I am concerned, the code I have written for this software
+ * can be used freely for any purpose.  Any derived versions of this
+ * software must be clearly marked as such, and if the derived work is
+ * incompatible with the protocol description in the RFC file, it must be
+ * called by a name other than "ssh" or "Secure Shell".
  */
 
 #include "includes.h"
-RCSID("$Id$");
 
-#include "packet.h"
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <unistd.h>
+
 #include "xmalloc.h"
-#include "ssh.h"
+#include "packet.h"
+#include "log.h"
+#include "canohost.h"
+#include "misc.h"
+
+static void check_ip_options(int, char *);
+static char *canonical_host_ip = NULL;
+static int cached_port = -1;
 
 /*
  * Return the canonical name of the host at the other end of the socket. The
  * caller should free the returned string with xfree.
  */
 
-char *
-get_remote_hostname(int socket)
+static char *
+get_remote_hostname(int sock, int use_dns)
 {
-       struct sockaddr_in from;
-       int fromlen, i;
-       struct hostent *hp;
-       char name[MAXHOSTNAMELEN];
+       struct sockaddr_storage from;
+       int i;
+       socklen_t fromlen;
+       struct addrinfo hints, *ai, *aitop;
+       char name[NI_MAXHOST], ntop[NI_MAXHOST], ntop2[NI_MAXHOST];
 
        /* Get IP address of client. */
        fromlen = sizeof(from);
        memset(&from, 0, sizeof(from));
-       if (getpeername(socket, (struct sockaddr *) & from, &fromlen) < 0) {
+       if (getpeername(sock, (struct sockaddr *)&from, &fromlen) < 0) {
                debug("getpeername failed: %.100s", strerror(errno));
-               fatal_cleanup();
+               cleanup_exit(255);
        }
+
+       if (from.ss_family == AF_INET)
+               check_ip_options(sock, ntop);
+
+       ipv64_normalise_mapped(&from, &fromlen);
+
+       if (from.ss_family == AF_INET6)
+               fromlen = sizeof(struct sockaddr_in6);
+
+       if (getnameinfo((struct sockaddr *)&from, fromlen, ntop, sizeof(ntop),
+           NULL, 0, NI_NUMERICHOST) != 0)
+               fatal("get_remote_hostname: getnameinfo NI_NUMERICHOST failed");
+
+       if (!use_dns)
+               return xstrdup(ntop);
+
+       debug3("Trying to reverse map address %.100s.", ntop);
        /* Map the IP address to a host name. */
-       hp = gethostbyaddr((char *) &from.sin_addr, sizeof(struct in_addr),
-                          from.sin_family);
-       if (hp) {
-               /* Got host name, find canonic host name. */
-               if (strchr(hp->h_name, '.') != 0)
-                       strlcpy(name, hp->h_name, sizeof(name));
-               else if (hp->h_aliases != 0
-                        && hp->h_aliases[0] != 0
-                        && strchr(hp->h_aliases[0], '.') != 0)
-                       strlcpy(name, hp->h_aliases[0], sizeof(name));
-               else
-                       strlcpy(name, hp->h_name, sizeof(name));
-
-               /*
-                * Convert it to all lowercase (which is expected by the rest
-                * of this software).
-                */
-               for (i = 0; name[i]; i++)
-                       if (isupper(name[i]))
-                               name[i] = tolower(name[i]);
-
-               /*
-                * Map it back to an IP address and check that the given
-                * address actually is an address of this host.  This is
-                * necessary because anyone with access to a name server can
-                * define arbitrary names for an IP address. Mapping from
-                * name to IP address can be trusted better (but can still be
-                * fooled if the intruder has access to the name server of
-                * the domain).
-                */
-               hp = gethostbyname(name);
-               if (!hp) {
-                       log("reverse mapping checking gethostbyname for %.700s failed - POSSIBLE BREAKIN ATTEMPT!", name);
-                       strlcpy(name, inet_ntoa(from.sin_addr), sizeof name);
-                       goto check_ip_options;
-               }
-               /* Look for the address from the list of addresses. */
-               for (i = 0; hp->h_addr_list[i]; i++)
-                       if (memcmp(hp->h_addr_list[i], &from.sin_addr, sizeof(from.sin_addr))
-                           == 0)
-                               break;
-               /*
-                * If we reached the end of the list, the address was not
-                * there.
-                */
-               if (!hp->h_addr_list[i]) {
-                       /* Address not found for the host name. */
-                       log("Address %.100s maps to %.600s, but this does not map back to the address - POSSIBLE BREAKIN ATTEMPT!",
-                           inet_ntoa(from.sin_addr), name);
-                       strlcpy(name, inet_ntoa(from.sin_addr), sizeof name);
-                       goto check_ip_options;
-               }
-               /* Address was found for the host name.  We accept the host name. */
-       } else {
-               /* Host name not found.  Use ascii representation of the address. */
-               strlcpy(name, inet_ntoa(from.sin_addr), sizeof name);
-               log("Could not reverse map address %.100s.", name);
+       if (getnameinfo((struct sockaddr *)&from, fromlen, name, sizeof(name),
+           NULL, 0, NI_NAMEREQD) != 0) {
+               /* Host name not found.  Use ip address. */
+               return xstrdup(ntop);
        }
 
-check_ip_options:
-
        /*
-        * If IP options are supported, make sure there are none (log and
-        * disconnect them if any are found).  Basically we are worried about
-        * source routing; it can be used to pretend you are somebody
-        * (ip-address) you are not. That itself may be "almost acceptable"
-        * under certain circumstances, but rhosts autentication is useless
-        * if source routing is accepted. Notice also that if we just dropped
-        * source routing here, the other side could use IP spoofing to do
-        * rest of the interaction and could still bypass security.  So we
-        * exit here if we detect any IP options.
+        * if reverse lookup result looks like a numeric hostname,
+        * someone is trying to trick us by PTR record like following:
+        *      1.1.1.10.in-addr.arpa.  IN PTR  2.3.4.5
         */
-       {
-               unsigned char options[200], *ucp;
-               char text[1024], *cp;
-               int option_size, ipproto;
-               struct protoent *ip;
-
-               if ((ip = getprotobyname("ip")) != NULL)
-                       ipproto = ip->p_proto;
-               else
-                       ipproto = IPPROTO_IP;
-               option_size = sizeof(options);
-               if (getsockopt(0, ipproto, IP_OPTIONS, (char *) options,
-                              &option_size) >= 0 && option_size != 0) {
-                       cp = text;
-                       /* Note: "text" buffer must be at least 3x as big as options. */
-                       for (ucp = options; option_size > 0; ucp++, option_size--, cp += 3)
-                               sprintf(cp, " %2.2x", *ucp);
-                       log("Connection from %.100s with IP options:%.800s",
-                           inet_ntoa(from.sin_addr), text);
-                       packet_disconnect("Connection from %.100s with IP options:%.800s",
-                                         inet_ntoa(from.sin_addr), text);
-               }
+       memset(&hints, 0, sizeof(hints));
+       hints.ai_socktype = SOCK_DGRAM; /*dummy*/
+       hints.ai_flags = AI_NUMERICHOST;
+       if (getaddrinfo(name, NULL, &hints, &ai) == 0) {
+               logit("Nasty PTR record \"%s\" is set up for %s, ignoring",
+                   name, ntop);
+               freeaddrinfo(ai);
+               return xstrdup(ntop);
        }
 
+       /*
+        * Convert it to all lowercase (which is expected by the rest
+        * of this software).
+        */
+       for (i = 0; name[i]; i++)
+               if (isupper(name[i]))
+                       name[i] = (char)tolower(name[i]);
+       /*
+        * Map it back to an IP address and check that the given
+        * address actually is an address of this host.  This is
+        * necessary because anyone with access to a name server can
+        * define arbitrary names for an IP address. Mapping from
+        * name to IP address can be trusted better (but can still be
+        * fooled if the intruder has access to the name server of
+        * the domain).
+        */
+       memset(&hints, 0, sizeof(hints));
+       hints.ai_family = from.ss_family;
+       hints.ai_socktype = SOCK_STREAM;
+       if (getaddrinfo(name, NULL, &hints, &aitop) != 0) {
+               logit("reverse mapping checking getaddrinfo for %.700s "
+                   "[%s] failed - POSSIBLE BREAK-IN ATTEMPT!", name, ntop);
+               return xstrdup(ntop);
+       }
+       /* Look for the address from the list of addresses. */
+       for (ai = aitop; ai; ai = ai->ai_next) {
+               if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop2,
+                   sizeof(ntop2), NULL, 0, NI_NUMERICHOST) == 0 &&
+                   (strcmp(ntop, ntop2) == 0))
+                               break;
+       }
+       freeaddrinfo(aitop);
+       /* If we reached the end of the list, the address was not there. */
+       if (!ai) {
+               /* Address not found for the host name. */
+               logit("Address %.100s maps to %.600s, but this does not "
+                   "map back to the address - POSSIBLE BREAK-IN ATTEMPT!",
+                   ntop, name);
+               return xstrdup(ntop);
+       }
        return xstrdup(name);
 }
 
-static char *canonical_host_name = NULL;
-static char *canonical_host_ip = NULL;
-
-/* Returns 1 if remote host is connected via socket, 0 if not. */
+/*
+ * If IP options are supported, make sure there are none (log and
+ * disconnect them if any are found).  Basically we are worried about
+ * source routing; it can be used to pretend you are somebody
+ * (ip-address) you are not. That itself may be "almost acceptable"
+ * under certain circumstances, but rhosts autentication is useless
+ * if source routing is accepted. Notice also that if we just dropped
+ * source routing here, the other side could use IP spoofing to do
+ * rest of the interaction and could still bypass security.  So we
+ * exit here if we detect any IP options.
+ */
+/* IPv4 only */
+static void
+check_ip_options(int sock, char *ipaddr)
+{
+#ifdef IP_OPTIONS
+       u_char options[200];
+       char text[sizeof(options) * 3 + 1];
+       socklen_t option_size;
+       u_int i;
+       int ipproto;
+       struct protoent *ip;
+
+       if ((ip = getprotobyname("ip")) != NULL)
+               ipproto = ip->p_proto;
+       else
+               ipproto = IPPROTO_IP;
+       option_size = sizeof(options);
+       if (getsockopt(sock, ipproto, IP_OPTIONS, options,
+           &option_size) >= 0 && option_size != 0) {
+               text[0] = '\0';
+               for (i = 0; i < option_size; i++)
+                       snprintf(text + i*3, sizeof(text) - i*3,
+                           " %2.2x", options[i]);
+               fatal("Connection from %.100s with IP options:%.800s",
+                   ipaddr, text);
+       }
+#endif /* IP_OPTIONS */
+}
 
-int
-peer_connection_is_on_socket()
+void
+ipv64_normalise_mapped(struct sockaddr_storage *addr, socklen_t *len)
 {
-       struct sockaddr_in from;
-       int fromlen;
-       int in = packet_get_connection_in();
-       int out = packet_get_connection_out();
-
-       /* filedescriptors in and out are the same, so it's a socket */
-       if (in == out)
-               return 1;
-       fromlen = sizeof(from);
-       memset(&from, 0, sizeof(from));
-       if (getpeername(in, (struct sockaddr *) & from, &fromlen) < 0)
-               return 0;
-       if (from.sin_family != AF_INET && from.sin_family != AF_INET6)
-               return 0;
-       return 1;
+       struct sockaddr_in6 *a6 = (struct sockaddr_in6 *)addr;
+       struct sockaddr_in *a4 = (struct sockaddr_in *)addr;
+       struct in_addr inaddr;
+       u_int16_t port;
+
+       if (addr->ss_family != AF_INET6 ||
+           !IN6_IS_ADDR_V4MAPPED(&a6->sin6_addr))
+               return;
+
+       debug3("Normalising mapped IPv4 in IPv6 address");
+
+       memcpy(&inaddr, ((char *)&a6->sin6_addr) + 12, sizeof(inaddr));
+       port = a6->sin6_port;
+
+       memset(addr, 0, sizeof(*a4));
+
+       a4->sin_family = AF_INET;
+       *len = sizeof(*a4);
+       memcpy(&a4->sin_addr, &inaddr, sizeof(inaddr));
+       a4->sin_port = port;
 }
 
 /*
@@ -172,94 +214,227 @@ peer_connection_is_on_socket()
  */
 
 const char *
-get_canonical_hostname()
+get_canonical_hostname(int use_dns)
 {
-       /* Check if we have previously retrieved this same name. */
-       if (canonical_host_name != NULL)
+       char *host;
+       static char *canonical_host_name = NULL;
+       static char *remote_ip = NULL;
+
+       /* Check if we have previously retrieved name with same option. */
+       if (use_dns && canonical_host_name != NULL)
                return canonical_host_name;
+       if (!use_dns && remote_ip != NULL)
+               return remote_ip;
 
        /* Get the real hostname if socket; otherwise return UNKNOWN. */
-       if (peer_connection_is_on_socket())
-               canonical_host_name = get_remote_hostname(packet_get_connection_in());
+       if (packet_connection_is_on_socket())
+               host = get_remote_hostname(packet_get_connection_in(), use_dns);
        else
-               canonical_host_name = xstrdup("UNKNOWN");
+               host = "UNKNOWN";
 
-       return canonical_host_name;
+       if (use_dns)
+               canonical_host_name = host;
+       else
+               remote_ip = host;
+       return host;
 }
 
 /*
- * Returns the IP-address of the remote host as a string.  The returned
- * string need not be freed.
+ * Returns the local/remote IP-address/hostname of socket as a string.
+ * The returned string must be freed.
  */
+static char *
+get_socket_address(int sock, int remote, int flags)
+{
+       struct sockaddr_storage addr;
+       socklen_t addrlen;
+       char ntop[NI_MAXHOST];
+       int r;
 
-const char *
-get_remote_ipaddr()
+       /* Get IP address of client. */
+       addrlen = sizeof(addr);
+       memset(&addr, 0, sizeof(addr));
+
+       if (remote) {
+               if (getpeername(sock, (struct sockaddr *)&addr, &addrlen)
+                   < 0)
+                       return NULL;
+       } else {
+               if (getsockname(sock, (struct sockaddr *)&addr, &addrlen)
+                   < 0)
+                       return NULL;
+       }
+
+       /* Work around Linux IPv6 weirdness */
+       if (addr.ss_family == AF_INET6)
+               addrlen = sizeof(struct sockaddr_in6);
+
+       ipv64_normalise_mapped(&addr, &addrlen);
+
+       /* Get the address in ascii. */
+       if ((r = getnameinfo((struct sockaddr *)&addr, addrlen, ntop,
+           sizeof(ntop), NULL, 0, flags)) != 0) {
+               error("get_socket_address: getnameinfo %d failed: %s", flags,
+                   ssh_gai_strerror(r));
+               return NULL;
+       }
+       return xstrdup(ntop);
+}
+
+char *
+get_peer_ipaddr(int sock)
+{
+       char *p;
+
+       if ((p = get_socket_address(sock, 1, NI_NUMERICHOST)) != NULL)
+               return p;
+       return xstrdup("UNKNOWN");
+}
+
+char *
+get_local_ipaddr(int sock)
 {
-       struct sockaddr_in from;
-       int fromlen, socket;
+       char *p;
 
-       /* Check whether we have chached the name. */
-       if (canonical_host_ip != NULL)
-               return canonical_host_ip;
+       if ((p = get_socket_address(sock, 0, NI_NUMERICHOST)) != NULL)
+               return p;
+       return xstrdup("UNKNOWN");
+}
 
-       /* If not a socket, return UNKNOWN. */
-       if (!peer_connection_is_on_socket()) {
-               canonical_host_ip = xstrdup("UNKNOWN");
-               return canonical_host_ip;
+char *
+get_local_name(int fd)
+{
+       char *host, myname[NI_MAXHOST];
+
+       /* Assume we were passed a socket */
+       if ((host = get_socket_address(fd, 0, NI_NAMEREQD)) != NULL)
+               return host;
+
+       /* Handle the case where we were passed a pipe */
+       if (gethostname(myname, sizeof(myname)) == -1) {
+               verbose("get_local_name: gethostname: %s", strerror(errno));
+       } else {
+               host = xstrdup(myname);
        }
-       /* Get client socket. */
-       socket = packet_get_connection_in();
 
-       /* Get IP address of client. */
-       fromlen = sizeof(from);
-       memset(&from, 0, sizeof(from));
-       if (getpeername(socket, (struct sockaddr *) & from, &fromlen) < 0) {
-               debug("getpeername failed: %.100s", strerror(errno));
-               fatal_cleanup();
+       return host;
+}
+
+void
+clear_cached_addr(void)
+{
+       if (canonical_host_ip != NULL) {
+               xfree(canonical_host_ip);
+               canonical_host_ip = NULL;
        }
-       /* Get the IP address in ascii. */
-       canonical_host_ip = xstrdup(inet_ntoa(from.sin_addr));
+       cached_port = -1;
+}
 
-       /* Return ip address string. */
+/*
+ * Returns the IP-address of the remote host as a string.  The returned
+ * string must not be freed.
+ */
+
+const char *
+get_remote_ipaddr(void)
+{
+       /* Check whether we have cached the ipaddr. */
+       if (canonical_host_ip == NULL) {
+               if (packet_connection_is_on_socket()) {
+                       canonical_host_ip =
+                           get_peer_ipaddr(packet_get_connection_in());
+                       if (canonical_host_ip == NULL)
+                               cleanup_exit(255);
+               } else {
+                       /* If not on socket, return UNKNOWN. */
+                       canonical_host_ip = xstrdup("UNKNOWN");
+               }
+       }
        return canonical_host_ip;
 }
 
-/* Returns the port of the peer of the socket. */
+const char *
+get_remote_name_or_ip(u_int utmp_len, int use_dns)
+{
+       static const char *remote = "";
+       if (utmp_len > 0)
+               remote = get_canonical_hostname(use_dns);
+       if (utmp_len == 0 || strlen(remote) > utmp_len)
+               remote = get_remote_ipaddr();
+       return remote;
+}
 
-int 
-get_peer_port(int sock)
+/* Returns the local/remote port for the socket. */
+
+int
+get_sock_port(int sock, int local)
 {
-       struct sockaddr_in from;
-       int fromlen;
+       struct sockaddr_storage from;
+       socklen_t fromlen;
+       char strport[NI_MAXSERV];
+       int r;
 
        /* Get IP address of client. */
        fromlen = sizeof(from);
        memset(&from, 0, sizeof(from));
-       if (getpeername(sock, (struct sockaddr *) & from, &fromlen) < 0) {
-               debug("getpeername failed: %.100s", strerror(errno));
-               fatal_cleanup();
+       if (local) {
+               if (getsockname(sock, (struct sockaddr *)&from, &fromlen) < 0) {
+                       error("getsockname failed: %.100s", strerror(errno));
+                       return 0;
+               }
+       } else {
+               if (getpeername(sock, (struct sockaddr *)&from, &fromlen) < 0) {
+                       debug("getpeername failed: %.100s", strerror(errno));
+                       return -1;
+               }
        }
+
+       /* Work around Linux IPv6 weirdness */
+       if (from.ss_family == AF_INET6)
+               fromlen = sizeof(struct sockaddr_in6);
+
        /* Return port number. */
-       return ntohs(from.sin_port);
+       if ((r = getnameinfo((struct sockaddr *)&from, fromlen, NULL, 0,
+           strport, sizeof(strport), NI_NUMERICSERV)) != 0)
+               fatal("get_sock_port: getnameinfo NI_NUMERICSERV failed: %s",
+                   ssh_gai_strerror(r));
+       return atoi(strport);
 }
 
-/* Returns the port number of the remote host.  */
+/* Returns remote/local port number for the current connection. */
 
-int 
-get_remote_port()
+static int
+get_port(int local)
 {
-       int socket;
-
        /*
         * If the connection is not a socket, return 65535.  This is
         * intentionally chosen to be an unprivileged port number.
         */
-       if (!peer_connection_is_on_socket())
+       if (!packet_connection_is_on_socket())
                return 65535;
 
-       /* Get client socket. */
-       socket = packet_get_connection_in();
+       /* Get socket and return the port number. */
+       return get_sock_port(packet_get_connection_in(), local);
+}
+
+int
+get_peer_port(int sock)
+{
+       return get_sock_port(sock, 0);
+}
+
+int
+get_remote_port(void)
+{
+       /* Cache to avoid getpeername() on a dead connection */
+       if (cached_port == -1)
+               cached_port = get_port(0);
 
-       /* Get and return the peer port number. */
-       return get_peer_port(socket);
+       return cached_port;
+}
+
+int
+get_local_port(void)
+{
+       return get_port(1);
 }
This page took 0.08103 seconds and 4 git commands to generate.