Use getaddrinfo() in kprop and kpropd, and recognize IPv6 addresses
authorGreg Hudson <ghudson@mit.edu>
Fri, 11 Jun 2010 21:03:03 +0000 (21:03 +0000)
committerGreg Hudson <ghudson@mit.edu>
Fri, 11 Jun 2010 21:03:03 +0000 (21:03 +0000)
when setting up krb5_address structures.  kpropd still only binds to
one socket to avoid the need for a select() loop, so we turn off
IPV6_V6ONLY on that socket to ensure that IPv4 connections will still
be accepted.

Based on a patch from Michael Stapelberg <michael@stapelberg.de>.

ticket: 6686

git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@24134 dc483132-0cff-0310-8789-dd5450dbe970

src/slave/Makefile.in
src/slave/kprop.c
src/slave/kprop.h
src/slave/kprop_sock.c [new file with mode: 0644]
src/slave/kpropd.c

index 20bef7cfbeaaef4fe4b24f5549083392e22eeaa1..66305622e6ea5b1ee29e5031245c472b0bf32ffa 100644 (file)
@@ -6,11 +6,11 @@ DEFS=
 
 all::  kprop kpropd kproplog
 
-CLIENTSRCS= $(srcdir)/kprop.c 
-CLIENTOBJS= kprop.o 
+CLIENTSRCS= $(srcdir)/kprop.c $(srcdir)/kprop_sock.c
+CLIENTOBJS= kprop.o kprop_sock.o
 
-SERVERSRCS= $(srcdir)/kpropd.c $(srcdir)/kpropd_rpc.c
-SERVEROBJS= kpropd.o kpropd_rpc.o
+SERVERSRCS= $(srcdir)/kpropd.c $(srcdir)/kpropd_rpc.c $(srcdir)/kprop_sock.c
+SERVEROBJS= kpropd.o kpropd_rpc.o kprop_sock.o
 
 LOGSRCS= $(srcdir)/kproplog.c
 LOGOBJS= kproplog.o
index 764b0f46be49a69f4801cf0f9b01d12d61c87531..22ac3a6a847c49974fc385f4f090315269945223 100644 (file)
@@ -59,20 +59,20 @@ char    *srvtab = 0;
 char    *slave_host;
 char    *realm = 0;
 char    *file = KPROP_DEFAULT_FILE;
-short   port = 0;
 
 krb5_principal  my_principal;           /* The Kerberos principal we'll be */
 /* running under, initialized in */
 /* get_tickets() */
 krb5_ccache     ccache;         /* Credentials cache which we'll be using */
 krb5_creds      creds;
-krb5_address    sender_addr;
-krb5_address    receiver_addr;
+krb5_address    *sender_addr;
+krb5_address    *receiver_addr;
+const char      *port = KPROP_SERVICE;
 
 void    PRS(int, char **);
 void    get_tickets(krb5_context);
 static void usage(void);
-krb5_error_code open_connection(char *, int *, char *, unsigned int);
+static void open_connection(krb5_context, char *, int *);
 void    kerberos_authenticate(krb5_context, krb5_auth_context *,
                               int, krb5_principal, krb5_creds **);
 int     open_database(krb5_context, char *, int *);
@@ -99,7 +99,6 @@ main(argc, argv)
     krb5_context context;
     krb5_creds *my_creds;
     krb5_auth_context auth_context;
-    char    Errmsg[256];
 
     retval = krb5_init_context(&context);
     if (retval) {
@@ -110,17 +109,7 @@ main(argc, argv)
     get_tickets(context);
 
     database_fd = open_database(context, file, &database_size);
-    retval = open_connection(slave_host, &fd, Errmsg, sizeof(Errmsg));
-    if (retval) {
-        com_err(progname, retval, "%s while opening connection to %s",
-                Errmsg, slave_host);
-        exit(1);
-    }
-    if (fd < 0) {
-        fprintf(stderr, "%s: %s while opening connection to %s\n",
-                progname, Errmsg, slave_host);
-        exit(1);
-    }
+    open_connection(context, slave_host, &fd);
     kerberos_authenticate(context, &auth_context, fd, my_principal,
                           &my_creds);
     xmit_database(context, auth_context, my_creds, fd, database_fd,
@@ -166,11 +155,8 @@ void PRS(argc, argv)
                     debug++;
                     break;
                 case 'P':
-                    if (*word)
-                        port = htons(atoi(word));
-                    else
-                        port = htons(atoi(*argv++));
-                    if (!port)
+                    port = (*word != '\0') ? word : *argv++;
+                    if (port == NULL)
                         usage();
                     word = 0;
                     break;
@@ -311,75 +297,72 @@ void get_tickets(context)
     }
 }
 
-krb5_error_code
-open_connection(host, fd, Errmsg, ErrmsgSz)
-    char            *host;
-    int             *fd;
-    char            *Errmsg;
-    unsigned int     ErrmsgSz;
+static void
+open_connection(krb5_context context, char *host, int *fd)
 {
     int     s;
     krb5_error_code retval;
-
-    struct hostent  *hp;
-    register struct servent *sp;
-    struct sockaddr_in my_sin;
     GETSOCKNAME_ARG3_TYPE socket_length;
+    struct addrinfo hints, *res, *answers;
+    struct sockaddr *sa;
+    struct sockaddr_storage my_sin;
+    int error;
+
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = PF_UNSPEC;
+    hints.ai_socktype = SOCK_STREAM;
+    error = getaddrinfo(host, port, &hints, &answers);
+    if (error != 0) {
+        com_err(progname, 0, "%s: %s", host, gai_strerror(error));
+        exit(1);
+    }
 
-    hp = gethostbyname(host);
-    if (hp == NULL) {
-        (void) snprintf(Errmsg, ErrmsgSz, "%s: unknown host", host);
-        *fd = -1;
-        return(0);
-    }
-    my_sin.sin_family = hp->h_addrtype;
-    memcpy(&my_sin.sin_addr, hp->h_addr, sizeof(my_sin.sin_addr));
-    if(!port) {
-        sp = getservbyname(KPROP_SERVICE, "tcp");
-        if (sp == 0) {
-            my_sin.sin_port = htons(KPROP_PORT);
-        } else {
-            my_sin.sin_port = sp->s_port;
+    s = -1;
+    retval = EINVAL;
+    for (res = answers; res != NULL; res = res->ai_next) {
+        s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+        if (s < 0) {
+            com_err(progname, errno, "while creating socket");
+            exit(1);
         }
-    } else
-        my_sin.sin_port = port;
-    s = socket(AF_INET, SOCK_STREAM, 0);
 
-    if (s < 0) {
-        (void) snprintf(Errmsg, ErrmsgSz, "in call to socket");
-        return(errno);
-    }
-    if (connect(s, (struct sockaddr *)&my_sin, sizeof my_sin) < 0) {
-        retval = errno;
-        close(s);
-        (void) snprintf(Errmsg, ErrmsgSz, "in call to connect");
-        return(retval);
+        if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
+            retval = errno;
+            close(s);
+            s = -1;
+            continue;
+        }
+
+        /* We successfully connect()ed */
+        *fd = s;
+        retval = sockaddr2krbaddr(context, res->ai_family, res->ai_addr,
+                                  &receiver_addr);
+        if (retval != 0) {
+            com_err(progname, retval, "while converting server address");
+            exit(1);
+        }
+
+        break;
     }
-    *fd = s;
 
-    /*
-     * Set receiver_addr and sender_addr.
-     */
-    receiver_addr.addrtype = ADDRTYPE_INET;
-    receiver_addr.length = sizeof(my_sin.sin_addr);
-    receiver_addr.contents = (krb5_octet *) malloc(sizeof(my_sin.sin_addr));
-    memcpy(receiver_addr.contents, &my_sin.sin_addr,
-           sizeof(my_sin.sin_addr));
+    freeaddrinfo(answers);
 
+    if (s == -1) {
+        com_err(progname, retval, "while connecting to server");
+        exit(1);
+    }
+
+    /* Set sender_addr. */
     socket_length = sizeof(my_sin);
     if (getsockname(s, (struct sockaddr *)&my_sin, &socket_length) < 0) {
-        retval = errno;
-        close(s);
-        (void) snprintf(Errmsg, ErrmsgSz, "in call to getsockname");
-        return(retval);
-    }
-    sender_addr.addrtype = ADDRTYPE_INET;
-    sender_addr.length = sizeof(my_sin.sin_addr);
-    sender_addr.contents = (krb5_octet *) malloc(sizeof(my_sin.sin_addr));
-    memcpy(sender_addr.contents, &my_sin.sin_addr,
-           sizeof(my_sin.sin_addr));
-
-    return(0);
+        com_err(progname, errno, "while getting local socket address");
+        exit(1);
+    }
+    sa = (struct sockaddr *) &my_sin;
+    if (sockaddr2krbaddr(context, sa->sa_family, sa, &sender_addr) != 0) {
+        com_err(progname, errno, "while converting local address");
+        exit(1);
+    }
 }
 
 
@@ -401,8 +384,8 @@ void kerberos_authenticate(context, auth_context, fd, me, new_creds)
     krb5_auth_con_setflags(context, *auth_context,
                            KRB5_AUTH_CONTEXT_DO_SEQUENCE);
 
-    retval = krb5_auth_con_setaddrs(context, *auth_context, &sender_addr,
-                                    &receiver_addr);
+    retval = krb5_auth_con_setaddrs(context, *auth_context, sender_addr,
+                                    receiver_addr);
     if (retval) {
         com_err(progname, retval, "in krb5_auth_con_setaddrs");
         exit(1);
index 4ab53de74f6d51044d59a8373f86b74ca0fbd24a..573014bcc53998ee37fffefbcd18d321b38ed9a7 100644 (file)
@@ -37,3 +37,6 @@
 #define KPROP_BUFSIZ 32768
 
 /* pathnames are in osconf.h, included via k5-int.h */
+
+int sockaddr2krbaddr(krb5_context context, int family, struct sockaddr *sa,
+                     krb5_address **dest);
diff --git a/src/slave/kprop_sock.c b/src/slave/kprop_sock.c
new file mode 100644 (file)
index 0000000..54479c9
--- /dev/null
@@ -0,0 +1,69 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * slave/kprop_sock.c
+ *
+ * Copyright (C) 2010 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Export of this software from the United States of America may
+ *   require a specific license from the United States Government.
+ *   It is the responsibility of any person or organization contemplating
+ *   export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission.  Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose.  It is provided "as is" without express
+ * or implied warranty.
+ *
+ *
+ * sockaddr2krbaddr() utility function used by kprop and kpropd.
+ */
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include "k5-int.h"
+#include "kprop.h"
+
+/*
+ * Convert an IPv4 or IPv6 socket address to a newly allocated krb5_address.
+ * There is similar code elsewhere in the tree, so this should possibly become
+ * a libkrb5 API in the future.
+ */
+krb5_error_code
+sockaddr2krbaddr(krb5_context context, int family, struct sockaddr *sa,
+                krb5_address **dest)
+{
+    krb5_address addr;
+
+    if (family == AF_INET) {
+       struct sockaddr_in *sa4 = (struct sockaddr_in *) sa;
+       addr.addrtype = ADDRTYPE_INET;
+       addr.length = sizeof(sa4->sin_addr);
+       addr.contents = (krb5_octet *) &sa4->sin_addr;
+    } else if (family == AF_INET6) {
+       struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *) sa;
+        if (IN6_IS_ADDR_V4MAPPED(&sa6->sin6_addr)) {
+            addr.addrtype = ADDRTYPE_INET;
+            addr.contents = (krb5_octet *) &sa6->sin6_addr + 12;
+            addr.length = 4;
+        } else {
+           addr.addrtype = ADDRTYPE_INET6;
+           addr.length = sizeof(sa6->sin6_addr);
+           addr.contents = (krb5_octet *) &sa6->sin6_addr;
+       }
+    } else
+       return KRB5_PROG_ATYPE_NOSUPP;
+
+    return krb5_copy_addr(context, &addr, dest);
+}
index f669d8bbbd97f83755981c515804fd0f0625a596..c931d43cd3bbf5d7967fb979a9eb869ef420785a 100644 (file)
@@ -146,9 +146,9 @@ char    *kdb5_util = KPROPD_DEFAULT_KDB5_UTIL;
 char    *kerb_database = NULL;
 char    *acl_file_name = KPROPD_ACL_FILE;
 
-krb5_address    sender_addr;
-krb5_address    receiver_addr;
-short           port = 0;
+krb5_address    *sender_addr;
+krb5_address    *receiver_addr;
+const char      *port = KPROP_SERVICE;
 
 char **db_args = NULL;
 int db_args_size = 0;
@@ -157,12 +157,8 @@ void    PRS(char**);
 int     do_standalone(iprop_role iproprole);
 void    doit(int);
 krb5_error_code do_iprop(kdb_log_context *log_ctx);
-void    kerberos_authenticate(
-    krb5_context,
-    int,
-    krb5_principal *,
-    krb5_enctype *,
-    struct sockaddr_in);
+void    kerberos_authenticate(krb5_context, int, krb5_principal *,
+                              krb5_enctype *, struct sockaddr_storage *);
 krb5_boolean authorized_principal(krb5_context, krb5_principal, krb5_enctype);
 void    recv_database(krb5_context, int, int, krb5_data *);
 void    load_database(krb5_context, char *, char *);
@@ -241,11 +237,11 @@ static void resync_alarm(int sn)
 
 int do_standalone(iprop_role iproprole)
 {
-    struct  sockaddr_in     my_sin, frominet;
-    struct servent *sp;
+    struct  sockaddr_in     frominet;
+    struct addrinfo hints, *res;
     int     finet, s;
     GETPEERNAME_ARG3_TYPE fromlen;
-    int     ret;
+    int ret, error, val;
     /*
      * Timer for accept/read calls, in case of network type errors.
      */
@@ -253,23 +249,34 @@ int do_standalone(iprop_role iproprole)
 
 retry:
 
-    finet = socket(AF_INET, SOCK_STREAM, 0);
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = AF_UNSPEC;
+    hints.ai_socktype = SOCK_STREAM;
+    hints.ai_flags = AI_PASSIVE;
+
+    error = getaddrinfo(NULL, port, &hints, &res);
+    if (error != 0) {
+        (void) fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(error));
+        exit(1);
+    }
+
+    finet = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
     if (finet < 0) {
         com_err(progname, errno, "while obtaining socket");
         exit(1);
     }
-    memset(&my_sin,0, sizeof(my_sin));
-    if(!port) {
-        sp = getservbyname(KPROP_SERVICE, "tcp");
-        if (sp == NULL) {
-            com_err(progname, 0, "%s/tcp: unknown service", KPROP_SERVICE);
-            my_sin.sin_port = htons(KPROP_PORT);
-        }
-        else my_sin.sin_port = sp->s_port;
-    } else {
-        my_sin.sin_port = port;
-    }
-    my_sin.sin_family = AF_INET;
+
+    val = 1;
+    if (setsockopt(finet, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0)
+        com_err(progname, errno, "while setting SO_REUSEADDR option");
+
+#ifdef IPV6_V6ONLY
+    /* Typically, res will be the IPv6 wildcard address.  Some systems, such as
+     * the *BSDs, don't accept IPv4 connections on this address by default. */
+    val = 0;
+    if (setsockopt(finet, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)) < 0)
+        com_err(progname, errno, "while unsetting IPV6_V6ONLY option");
+#endif
 
     /*
      * We need to close the socket immediately if iprop is enabled,
@@ -277,13 +284,8 @@ retry:
      * linger around for too long
      */
     if (iproprole == IPROP_SLAVE) {
-        int on = 1;
         struct linger linger;
 
-        if (setsockopt(finet, SOL_SOCKET, SO_REUSEADDR,
-                       (char *)&on, sizeof(on)) < 0)
-            com_err(progname, errno,
-                    _("while setting socket option (SO_REUSEADDR)"));
         linger.l_onoff = 1;
         linger.l_linger = 2;
         if (setsockopt(finet, SOL_SOCKET, SO_LINGER,
@@ -308,22 +310,9 @@ retry:
         }
         backoff_timer *= 2;
     }
-    if ((ret = bind(finet, (struct sockaddr *) &my_sin, sizeof(my_sin))) < 0) {
-        if (debug) {
-            int on = 1;
-            fprintf(stderr,
-                    "%s: attempting to rebind socket with SO_REUSEADDR\n",
-                    progname);
-            if (setsockopt(finet, SOL_SOCKET, SO_REUSEADDR,
-                           (char *)&on, sizeof(on)) < 0)
-                com_err(progname, errno, "in setsockopt(SO_REUSEADDR)");
-            ret = bind(finet, (struct sockaddr *) &my_sin, sizeof(my_sin));
-        }
-        if (ret < 0) {
-            perror("bind");
-            com_err(progname, errno, "while binding listener socket");
-            exit(1);
-        }
+    if ((ret = bind(finet, res->ai_addr, res->ai_addrlen)) < 0) {
+        com_err(progname, errno, "while binding listener socket");
+        exit(1);
     }
     if (!debug && iproprole != IPROP_SLAVE)
         daemon(1, 0);
@@ -419,16 +408,16 @@ retry:
 void doit(fd)
     int     fd;
 {
-    struct sockaddr_in from;
+    struct sockaddr_storage from;
     int on = 1;
     GETPEERNAME_ARG3_TYPE fromlen;
-    struct hostent  *hp;
     krb5_error_code retval;
     krb5_data confmsg;
     int lock_fd;
     mode_t omask;
     krb5_enctype etype;
     int database_fd;
+    char host[INET6_ADDRSTRLEN+1];
 
     if (kpropd_context->kdblog_context &&
         kpropd_context->kdblog_context->iproprole == IPROP_SLAVE) {
@@ -468,23 +457,17 @@ void doit(fd)
                 "while attempting setsockopt (SO_KEEPALIVE)");
     }
 
-    if (!(hp = gethostbyaddr((char *) &(from.sin_addr.s_addr), fromlen,
-                             AF_INET))) {
-        syslog(LOG_INFO, "Connection from %s",
-               inet_ntoa(from.sin_addr));
-        if (debug)
-            printf("Connection from %s\n",
-                   inet_ntoa(from.sin_addr));
-    } else {
-        syslog(LOG_INFO, "Connection from %s", hp->h_name);
+    if (getnameinfo((const struct sockaddr *) &from, fromlen,
+                    host, sizeof(host), NULL, 0, 0) == 0) {
+        syslog(LOG_INFO, "Connection from %s", host);
         if (debug)
-            printf("Connection from %s\n", hp->h_name);
+            printf("Connection from %s\n", host);
     }
 
     /*
      * Now do the authentication
      */
-    kerberos_authenticate(kpropd_context, fd, &client, &etype, from);
+    kerberos_authenticate(kpropd_context, fd, &client, &etype, &from);
 
     /*
      * Turn off alarm upon successful authentication from master.
@@ -1070,11 +1053,8 @@ void PRS(argv)
                     word = 0;
                     break;
                 case 'P':
-                    if (*word)
-                        port = htons(atoi(word));
-                    else
-                        port = htons(atoi(*argv++));
-                    if (!port)
+                    port = (*word != '\0') ? word : *argv++;
+                    if (port == NULL)
                         usage();
                     word = 0;
                     break;
@@ -1216,22 +1196,19 @@ kerberos_authenticate(context, fd, clientp, etype, my_sin)
     int                   fd;
     krb5_principal      * clientp;
     krb5_enctype        * etype;
-    struct sockaddr_in    my_sin;
+    struct sockaddr_storage * my_sin;
 {
     krb5_error_code       retval;
     krb5_ticket         * ticket;
-    struct sockaddr_in    r_sin;
+    struct sockaddr_storage  r_sin;
     GETSOCKNAME_ARG3_TYPE sin_length;
     krb5_keytab           keytab = NULL;
 
     /*
      * Set recv_addr and send_addr
      */
-    sender_addr.addrtype = ADDRTYPE_INET;
-    sender_addr.length = sizeof(my_sin.sin_addr);
-    sender_addr.contents = (krb5_octet *) malloc(sizeof(my_sin.sin_addr));
-    memcpy(sender_addr.contents, &my_sin.sin_addr,
-           sizeof(my_sin.sin_addr));
+    sockaddr2krbaddr(context, my_sin->ss_family, (struct sockaddr *) my_sin,
+                     &sender_addr);
 
     sin_length = sizeof(r_sin);
     if (getsockname(fd, (struct sockaddr *) &r_sin, &sin_length)) {
@@ -1239,11 +1216,8 @@ kerberos_authenticate(context, fd, clientp, etype, my_sin)
         exit(1);
     }
 
-    receiver_addr.addrtype = ADDRTYPE_INET;
-    receiver_addr.length = sizeof(r_sin.sin_addr);
-    receiver_addr.contents = (krb5_octet *) malloc(sizeof(r_sin.sin_addr));
-    memcpy(receiver_addr.contents, &r_sin.sin_addr,
-           sizeof(r_sin.sin_addr));
+    sockaddr2krbaddr(context, r_sin.ss_family, (struct sockaddr *) &r_sin,
+                     &receiver_addr);
 
     if (debug) {
         char *name;
@@ -1272,8 +1246,8 @@ kerberos_authenticate(context, fd, clientp, etype, my_sin)
         exit(1);
     }
 
-    retval = krb5_auth_con_setaddrs(context, auth_context, &receiver_addr,
-                                    &sender_addr);
+    retval = krb5_auth_con_setaddrs(context, auth_context, receiver_addr,
+                                    sender_addr);
     if (retval) {
         syslog(LOG_ERR, "Error in krb5_auth_con_setaddrs: %s",
                error_message(retval));