Blob Blame History Raw
A couple of other changes that were made for OTP support when it was merged,
extracted and applied after the OTP backport:
* Read the secret that we share with the RADIUS server from the file named by
  the "secret" configuration setting.
* Log an error if we can't parse the OTP configuration string for a given
  principal entry.
* Pass back the error code if attempting to read the OTP configuration string
  for an entry produces one.
* When setting the remote address on the socket we're using to talk to the
  RADIUS server, wait for the non-blocking connect() to complete by waiting
  for it to become writable instead of readable.
--- krb5-1.11.3/src/include/k5-int.h
+++ krb5-1.11.3/src/include/k5-int.h
@@ -2612,6 +2612,17 @@ k5memdup(const void *in, size_t len, krb
     return ptr;
 }
 
+/* Like k5memdup, but add a final null byte. */
+static inline void *
+k5memdup0(const void *in, size_t len, krb5_error_code *code)
+{
+    void *ptr = k5alloc(len + 1, code);
+
+    if (ptr != NULL)
+        memcpy(ptr, in, len);
+    return ptr;
+}
+
 krb5_error_code KRB5_CALLCONV
 krb5_get_credentials_for_user(krb5_context context, krb5_flags options,
                               krb5_ccache ccache,
--- krb5-1.11.3/src/plugins/preauth/otp/otp_state.c
+++ krb5-1.11.3/src/plugins/preauth/otp/otp_state.c
@@ -43,6 +43,7 @@
 #define DEFAULT_SOCKET_FMT KDC_DIR "/%s.socket"
 #define DEFAULT_TIMEOUT 5
 #define DEFAULT_RETRIES 3
+#define MAX_SECRET_LEN 1024
 
 typedef struct token_type_st {
     char *name;
@@ -76,6 +77,52 @@ struct otp_state_st {
 
 static void request_send(request *req);
 
+static krb5_error_code
+read_secret_file(const char *secret_file, char **secret)
+{
+    char buf[MAX_SECRET_LEN];
+    krb5_error_code retval;
+    char *filename;
+    FILE *file;
+    int i, j;
+
+    *secret = NULL;
+
+    retval = k5_path_join(KDC_DIR, secret_file, &filename);
+    if (retval != 0) {
+        com_err("otp", retval, "Unable to resolve secret file '%s'", filename);
+        return retval;
+    }
+
+    file = fopen(filename, "r");
+    if (file == NULL) {
+        retval = errno;
+        com_err("otp", retval, "Unable to open secret file '%s'", filename);
+        return retval;
+    }
+
+    if (fgets(buf, sizeof(buf), file) == NULL)
+        retval = EIO;
+    fclose(file);
+    if (retval != 0) {
+        com_err("otp", retval, "Unable to read secret file '%s'", filename);
+        return retval;
+    }
+
+    /* Strip whitespace. */
+    for (i = 0; buf[i] != '\0'; i++) {
+        if (!isspace(buf[i]))
+            break;
+    }
+    for (j = strlen(buf) - i; j > 0; j--) {
+        if (!isspace(buf[j - 1]))
+            break;
+    }
+
+    *secret = k5memdup0(&buf[i], j - i, &retval);
+    return retval;
+}
+
 /* Free the contents of a single token type. */
 static void
 token_type_free(token_type *type)
@@ -125,8 +172,7 @@ static krb5_error_code
 token_type_decode(profile_t profile, const char *name, token_type *out)
 {
     krb5_error_code retval;
-    char *server = NULL, *name_copy = NULL, *secret = NULL;
-    const char *default_secret;
+    char *server = NULL, *name_copy = NULL, *secret = NULL, *pstr = NULL;
     int strip_realm, timeout, retries;
 
     memset(out, 0, sizeof(*out));
@@ -152,16 +198,27 @@ token_type_decode(profile_t profile, con
     }
 
     /* Get the secret (optional for Unix-domain sockets). */
-    default_secret = (*server == '/') ? "" : NULL;
-    retval = profile_get_string(profile, "otp", name, "secret", default_secret,
-                                &secret);
+    retval = profile_get_string(profile, "otp", name, "secret", NULL, &pstr);
     if (retval != 0)
         goto cleanup;
-    if (secret == NULL) {
-        com_err("otp", EINVAL, "Secret not specified in token type '%s'",
-		name);
-        retval = EINVAL;
-        goto cleanup;
+    if (pstr != NULL) {
+        retval = read_secret_file(pstr, &secret);
+        profile_release_string(pstr);
+        if (retval != 0)
+            goto cleanup;
+    } else {
+        if (server[0] != '/') {
+            com_err("otp", EINVAL, "Secret missing (token type '%s')", name);
+            retval = EINVAL;
+            goto cleanup;
+        }
+
+        /* Use the default empty secret for UNIX domain stream sockets. */
+        secret = strdup("");
+        if (secret == NULL) {
+            retval = ENOMEM;
+            goto cleanup;
+        }
     }
 
     /* Get the timeout (profile value in seconds, result in milliseconds). */
@@ -521,6 +578,7 @@ otp_state_verify(otp_state *state, verto
 {
     krb5_error_code retval;
     request *rqst = NULL;
+    char *name;
 
     if (state->radius == NULL) {
         retval = krad_client_new(state->ctx, ctx, &state->radius);
@@ -548,8 +606,14 @@ otp_state_verify(otp_state *state, verto
 
     retval = tokens_decode(state->ctx, princ, state->types, config,
                            &rqst->tokens);
-    if (retval != 0)
+    if (retval != 0) {
+        if (krb5_unparse_name(state->ctx, princ, &name) == 0) {
+            com_err("otp", retval,
+                    "Can't decode otp config string for principal '%s'", name);
+            krb5_free_unparsed_name(state->ctx, name);
+        }
         goto error;
+    }
 
     request_send(rqst);
     return;
--- krb5-1.11.3/src/plugins/preauth/otp/main.c
+++ krb5-1.11.3/src/plugins/preauth/otp/main.c
@@ -204,7 +204,9 @@ otp_edata(krb5_context context, krb5_kdc
 
     /* Determine if otp is enabled for the user. */
     retval = cb->get_string(context, rock, "otp", &config);
-    if (retval != 0 || config == NULL)
+    if (retval == 0 && config == NULL)
+        retval = ENOENT;
+    if (retval != 0)
         goto out;
     cb->free_string(context, rock, config);
 
@@ -305,7 +307,7 @@ otp_verify(krb5_context context, krb5_da
 
     /* Get the principal's OTP configuration string. */
     retval = cb->get_string(context, rock, "otp", &config);
-    if (config == NULL)
+    if (retval == 0 && config == NULL)
         retval = KRB5_PREAUTH_FAILED;
     if (retval != 0) {
         free(rs);
--- krb5-1.11.3/src/lib/krad/remote.c
+++ krb5-1.11.3/src/lib/krad/remote.c
@@ -36,9 +36,10 @@
 
 #include <sys/un.h>
 
-#define FLAGS_READ (VERTO_EV_FLAG_PERSIST | VERTO_EV_FLAG_IO_CLOSE_FD | \
-                    VERTO_EV_FLAG_IO_ERROR | VERTO_EV_FLAG_IO_READ)
-#define FLAGS_WRITE (FLAGS_READ | VERTO_EV_FLAG_IO_WRITE)
+#define FLAGS_NONE  VERTO_EV_FLAG_NONE
+#define FLAGS_READ  VERTO_EV_FLAG_IO_READ
+#define FLAGS_WRITE VERTO_EV_FLAG_IO_WRITE
+#define FLAGS_BASE  VERTO_EV_FLAG_PERSIST | VERTO_EV_FLAG_IO_ERROR
 
 TAILQ_HEAD(request_head, request_st);
 
@@ -58,6 +59,7 @@
 struct krad_remote_st {
     krb5_context kctx;
     verto_ctx *vctx;
+    int fd;
     verto_ev *io;
     char *secret;
     struct addrinfo *info;
@@ -69,6 +71,9 @@
 static void
 on_io(verto_ctx *ctx, verto_ev *ev);
 
+static void
+on_timeout(verto_ctx *ctx, verto_ev *ev);
+
 /* Iterate over the set of outstanding packets. */
 static const krad_packet *
 iterator(request **out)
@@ -121,91 +126,131 @@
     }
 }
 
-/* Handle when packets receive no response within their alloted time. */
-static void
-on_timeout(verto_ctx *ctx, verto_ev *ev)
+/* Start the timeout timer for the request. */
+static krb5_error_code
+request_start_timer(request *r, verto_ctx *vctx)
 {
-    request *req = verto_get_private(ev);
+    verto_del(r->timer);
 
-    req->timer = NULL;          /* Void the timer event. */
+    r->timer = verto_add_timeout(vctx, VERTO_EV_FLAG_NONE, on_timeout,
+                                 r->timeout);
+    if (r->timer != NULL)
+        verto_set_private(r->timer, r, NULL);
 
-    /* If we have more retries to perform, resend the packet. */
-    if (req->retries-- > 1) {
-        req->sent = 0;
-        verto_set_flags(req->rr->io, FLAGS_WRITE);
-        return;
-    }
+    return (r->timer == NULL) ? ENOMEM : 0;
+}
 
-    request_finish(req, ETIMEDOUT, NULL);
+/* Disconnect from the remote host. */
+static void
+remote_disconnect(krad_remote *rr)
+{
+    close(rr->fd);
+    verto_del(rr->io);
+    rr->fd = -1;
+    rr->io = NULL;
 }
 
-/* Connect to the remote host. */
+/* Add the specified flags to the remote. This automatically manages the
+ * lifecyle of the underlying event. Also connects if disconnected. */
 static krb5_error_code
-remote_connect(krad_remote *rr)
+remote_add_flags(krad_remote *remote, verto_ev_flag flags)
 {
-    int i, sock = -1;
-    verto_ev *ev;
+    verto_ev_flag curflags = VERTO_EV_FLAG_NONE;
+    int i;
 
-    sock = socket(rr->info->ai_family, rr->info->ai_socktype,
-                  rr->info->ai_protocol);
-    if (sock < 0)
-        return errno;
+    flags &= (FLAGS_READ | FLAGS_WRITE);
+    if (remote == NULL || flags == FLAGS_NONE)
+        return EINVAL;
+
+    /* If there is no connection, connect. */
+    if (remote->fd < 0) {
+        verto_del(remote->io);
+        remote->io = NULL;
+
+        remote->fd = socket(remote->info->ai_family, remote->info->ai_socktype,
+                            remote->info->ai_protocol);
+        if (remote->fd < 0)
+            return errno;
 
-    i = connect(sock, rr->info->ai_addr, rr->info->ai_addrlen);
-    if (i < 0) {
-        i = errno;
-        close(sock);
-        return i;
+        i = connect(remote->fd, remote->info->ai_addr,
+                    remote->info->ai_addrlen);
+        if (i < 0) {
+            i = errno;
+            remote_disconnect(remote);
+            return i;
+        }
     }
 
-    ev = verto_add_io(rr->vctx, FLAGS_READ, on_io, sock);
-    if (ev == NULL) {
-        close(sock);
-        return ENOMEM;
+    if (remote->io == NULL) {
+        remote->io = verto_add_io(remote->vctx, FLAGS_BASE | flags,
+                                  on_io, remote->fd);
+        if (remote->io == NULL)
+            return ENOMEM;
+        verto_set_private(remote->io, remote, NULL);
     }
 
-    rr->io = ev;
-    verto_set_private(rr->io, rr, NULL);
+    curflags = verto_get_flags(remote->io);
+    if ((curflags & flags) != flags)
+        verto_set_flags(remote->io, FLAGS_BASE | curflags | flags);
+
     return 0;
 }
 
-/* Disconnect and reconnect to the remote host. */
-static krb5_error_code
-remote_reconnect(krad_remote *rr, int errnum)
+/* Remove the specified flags to the remote. This automatically manages the
+ * lifecyle of the underlying event. */
+static void
+remote_del_flags(krad_remote *remote, verto_ev_flag flags)
+{
+    if (remote == NULL || remote->io == NULL)
+        return;
+
+    flags = verto_get_flags(remote->io) & (FLAGS_READ | FLAGS_WRITE) & ~flags;
+    if (flags == FLAGS_NONE) {
+        verto_del(remote->io);
+        remote->io = NULL;
+        return;
+    }
+
+    verto_set_flags(remote->io, FLAGS_BASE | flags);
+}
+
+/* Close the connection and start the timers of all outstanding requests. */
+static void
+remote_shutdown(krad_remote *rr)
 {
     krb5_error_code retval;
-    const krb5_data *tmp;
     request *r;
 
-    verto_del(rr->io);
-    rr->io = NULL;
-    retval = remote_connect(rr);
-    if (retval != 0)
-        return retval;
+    remote_disconnect(rr);
 
+    /* Start timers for all unsent packets. */
     TAILQ_FOREACH(r, &rr->list, list) {
-        tmp = krad_packet_encode(r->request);
-
-        if (r->sent == tmp->length) {
-            /* Error out sent requests. */
-            request_finish(r, errnum, NULL);
-        } else {
-            /* Reset partially sent requests. */
-            r->sent = 0;
+        if (r->timer == NULL) {
+            retval = request_start_timer(r, rr->vctx);
+            if (retval != 0)
+                request_finish(r, retval, NULL);
         }
     }
-
-    return 0;
 }
 
-/* Close the connection and call the callbacks of all oustanding requests. */
+/* Handle when packets receive no response within their alloted time. */
 static void
-remote_shutdown(krad_remote *rr, int errnum)
+on_timeout(verto_ctx *ctx, verto_ev *ev)
 {
-    verto_del(rr->io);
-    rr->io = NULL;
-    while (!TAILQ_EMPTY(&rr->list))
-        request_finish(TAILQ_FIRST(&rr->list), errnum, NULL);
+    request *req = verto_get_private(ev);
+    krb5_error_code retval = ETIMEDOUT;
+
+    req->timer = NULL;          /* Void the timer event. */
+
+    /* If we have more retries to perform, resend the packet. */
+    if (req->retries-- > 1) {
+        req->sent = 0;
+        retval = remote_add_flags(req->rr, FLAGS_WRITE);
+        if (retval == 0)
+            return;
+    }
+
+    request_finish(req, retval, NULL);
 }
 
 /* Write data to the socket. */
@@ -213,8 +258,8 @@
 on_io_write(krad_remote *rr)
 {
     const krb5_data *tmp;
+    ssize_t written;
     request *r;
-    int i;
 
     TAILQ_FOREACH(r, &rr->list, list) {
         tmp = krad_packet_encode(r->request);
@@ -224,45 +269,38 @@
             continue;
 
         /* Send the packet. */
-        i = sendto(verto_get_fd(rr->io), tmp->data + r->sent,
-                   tmp->length - r->sent, 0, NULL, 0);
-        if (i < 0) {
+        written = sendto(verto_get_fd(rr->io), tmp->data + r->sent,
+                         tmp->length - r->sent, 0, NULL, 0);
+        if (written < 0) {
             /* Should we try again? */
             if (errno == EWOULDBLOCK || errno == EAGAIN || errno == ENOBUFS ||
                 errno == EINTR)
                 return;
 
-            /* In this case, we need to re-connect. */
-            i = remote_reconnect(rr, errno);
-            if (i == 0)
-                return;
-
-            /* Do a full reset. */
-            remote_shutdown(rr, i);
+            /* This error can't be worked around. */
+            remote_shutdown(rr);
             return;
         }
 
-        /* SOCK_STREAM permits partial writes. */
-        if (rr->info->ai_socktype == SOCK_STREAM)
-            r->sent += i;
-        else if (i == (int)tmp->length)
-            r->sent = i;
-
         /* If the packet was completely sent, set a timeout. */
+        r->sent += written;
         if (r->sent == tmp->length) {
-            verto_del(r->timer);
-            r->timer = verto_add_timeout(rr->vctx, VERTO_EV_FLAG_NONE,
-                                         on_timeout, r->timeout);
-            if (r->timer == NULL)
+            if (request_start_timer(r, rr->vctx) != 0) {
                 request_finish(r, ENOMEM, NULL);
-            else
-                verto_set_private(r->timer, r, NULL);
+                return;
+            }
+
+            if (remote_add_flags(rr, FLAGS_READ) != 0) {
+                remote_shutdown(rr);
+                return;
+            }
         }
 
         return;
     }
 
-    verto_set_flags(rr->io, FLAGS_READ);
+    remote_del_flags(rr, FLAGS_WRITE);
+    return;
 }
 
 /* Read data from the socket. */
@@ -280,9 +318,9 @@
     if (rr->info->ai_socktype == SOCK_STREAM) {
         pktlen = krad_packet_bytes_needed(&rr->buffer);
         if (pktlen < 0) {
-            retval = remote_reconnect(rr, EBADMSG);
-            if (retval != 0)
-                remote_shutdown(rr, retval);
+            /* If we received a malformed packet on a stream socket,
+             * assume the socket to be unrecoverable. */
+            remote_shutdown(rr);
             return;
         }
     }
@@ -295,26 +333,11 @@
         if (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR)
             return;
 
-        if (errno == ECONNREFUSED || errno == ECONNRESET ||
-            errno == ENOTCONN) {
-            /*
-             * When doing UDP against a local socket, the kernel will notify
-             * when the daemon closes. But not against remote sockets. We want
-             * to treat them both the same. Returning here will cause an
-             * eventual timeout.
-             */
-            if (rr->info->ai_socktype != SOCK_STREAM)
-                return;
-        }
-
-        /* In this case, we need to re-connect. */
-        i = remote_reconnect(rr, errno);
-        if (i == 0)
-           return;
-
-        /* Do a full reset. */
-        remote_shutdown(rr, i);
+        /* The socket is unrecoverable. */
+        remote_shutdown(rr);
         return;
+    } else if (i == 0) {
+        remote_del_flags(rr, FLAGS_READ);
     }
 
     /* If we have a partial read or just the header, try again. */
@@ -374,6 +397,7 @@
     tmp->vctx = vctx;
     tmp->buffer = make_data(tmp->buffer_, 0);
     TAILQ_INIT(&tmp->list);
+    tmp->fd = -1;
 
     tmp->secret = strdup(secret);
     if (tmp->secret == NULL)
@@ -389,10 +413,6 @@
     tmp->info->ai_next = NULL;
     tmp->info->ai_canonname = NULL;
 
-    retval = remote_connect(tmp);
-    if (retval != 0)
-        goto error;
-
     *rr = tmp;
     return 0;
 
@@ -414,7 +434,7 @@
     if (rr->info != NULL)
         free(rr->info->ai_addr);
     free(rr->info);
-    verto_del(rr->io);
+    remote_disconnect(rr);
     free(rr);
 }
 
@@ -440,21 +460,14 @@
         }
     }
 
-    if (rr->io == NULL) {
-        retval = remote_connect(rr);
-        if (retval != 0)
-            goto error;
-    }
-
-    if (rr->info->ai_socktype == SOCK_STREAM)
-        retries = 0;
     timeout = timeout / (retries + 1);
     retval = request_new(rr, tmp, timeout, retries, cb, data, &r);
     if (retval != 0)
         goto error;
 
-    if ((verto_get_flags(rr->io) & VERTO_EV_FLAG_IO_WRITE) == 0)
-        verto_set_flags(rr->io, FLAGS_WRITE);
+    retval = remote_add_flags(rr, FLAGS_WRITE);
+    if (retval != 0)
+        goto error;
 
     TAILQ_INSERT_TAIL(&rr->list, r, list);
     if (pkt != NULL)
--- krb5-1.11.3/src/lib/krad/t_attr.c
+++ krb5-1.11.3/src/lib/krad/t_attr.c
@@ -29,7 +29,7 @@
 
 #include "t_test.h"
 
-const static char encoded[] = {
+const static unsigned char encoded[] = {
     0xba, 0xfc, 0xed, 0x50, 0xe1, 0xeb, 0xa6, 0xc3,
     0xc1, 0x75, 0x20, 0xe9, 0x10, 0xce, 0xc2, 0xcb
 };
--- krb5-1.11.3/src/lib/krad/t_attrset.c
+++ krb5-1.11.3/src/lib/krad/t_attrset.c
@@ -34,7 +34,7 @@
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
 };
 
-const static char encpass[] = {
+const static unsigned char encpass[] = {
     0x58, 0x8d, 0xff, 0xda, 0x37, 0xf9, 0xe4, 0xca,
     0x19, 0xae, 0x49, 0xb7, 0x16, 0x6d, 0x58, 0x27
 };
--- krb5-1.11.3/src/lib/krad/t_daemon.h
+++ krb5-1.11.3/src/lib/krad/t_daemon.h
@@ -51,8 +51,8 @@
 static krb5_boolean
 daemon_start(int argc, const char **argv)
 {
-    sigset_t set;
-    int sig;
+    int fds[2];
+    char buf[1];
 
     if (argc != 3 || argv == NULL)
         return FALSE;
@@ -60,30 +60,23 @@
     if (daemon_pid != 0)
         return TRUE;
 
-    if (sigemptyset(&set) != 0)
-        return FALSE;
-
-    if (sigaddset(&set, SIGUSR1) != 0)
-        return FALSE;
-
-    if (sigaddset(&set, SIGCHLD) != 0)
-        return FALSE;
-
-    if (sigprocmask(SIG_BLOCK, &set, NULL) != 0)
+    if (pipe(fds) != 0)
         return FALSE;
 
+    /* Start the child process with the write end of the pipe as stdout. */
     daemon_pid = fork();
     if (daemon_pid == 0) {
-        close(STDOUT_FILENO);
-        open("/dev/null", O_WRONLY);
+        dup2(fds[1], STDOUT_FILENO);
+        close(fds[0]);
+        close(fds[1]);
         exit(execlp(argv[1], argv[1], argv[2], NULL));
     }
+    close(fds[1]);
 
-    if (sigwait(&set, &sig) != 0 || sig == SIGCHLD) {
-        daemon_stop();
-        daemon_pid = 0;
+    /* The child will write a sentinel character when it is listening. */
+    if (read(fds[0], buf, 1) != 1 || *buf != '~')
         return FALSE;
-    }
+    close(fds[0]);
 
     atexit(daemon_stop);
     return TRUE;
--- krb5-1.11.3/src/lib/krad/t_daemon.py
+++ krb5-1.11.3/src/lib/krad/t_daemon.py
@@ -33,7 +33,7 @@
 try:
     from pyrad import dictionary, packet, server
 except ImportError:
-    sys.stdout.write("pyrad not found!\n")
+    sys.stderr.write("pyrad not found!\n")
     sys.exit(0)
 
 # We could use a dictionary file, but since we need
@@ -49,28 +49,25 @@
         server.Server._HandleAuthPacket(self, pkt)
 
         passwd = []
-  
-        print "Request: "
+
         for key in pkt.keys():
             if key == "User-Password":
                 passwd = map(pkt.PwDecrypt, pkt[key])
-                print "\t%s\t%s" % (key, passwd)
-            else:
-                print "\t%s\t%s" % (key, pkt[key])
-  
+
         reply = self.CreateReplyPacket(pkt)
         if passwd == ['accept']:
             reply.code = packet.AccessAccept
-            print "Response: %s" % "Access-Accept"
         else:
             reply.code = packet.AccessReject
-            print "Response: %s" % "Access-Reject"
-        print
         self.SendReplyPacket(pkt.fd, reply)
 
 srv = TestServer(addresses=["localhost"],
                  hosts={"127.0.0.1":
                         server.RemoteHost("127.0.0.1", "foo", "localhost")},
                  dict=dictionary.Dictionary(StringIO.StringIO(DICTIONARY)))
-os.kill(os.getppid(), signal.SIGUSR1)
+
+# Write a sentinel character to let the parent process know we're listening.
+sys.stdout.write("~")
+sys.stdout.flush()
+
 srv.Run()