]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
third_party/heimdal: import lorikeet-heimdal-202210310104 (commit 0fc20ff4144973047e6...
authorAndrew Bartlett <abartlet@samba.org>
Mon, 31 Oct 2022 01:33:09 +0000 (14:33 +1300)
committerAndrew Bartlett <abartlet@samba.org>
Wed, 2 Nov 2022 04:23:34 +0000 (04:23 +0000)
This commit won't compile on it's own, as we need to fix the build system
to cope in the next commit.

The purpose of this commit is to update to a new lorikeet-heimdal tree
that includes the previous two patches and is rebased on a current
Heimdal master snapshot.

Signed-off-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Joseph Sutton <josephsutton@catalyst.net.nz>
154 files changed:
third_party/heimdal/.github/workflows/osx.yml
third_party/heimdal/.github/workflows/windows.yml
third_party/heimdal/admin/Makefile.am
third_party/heimdal/admin/add.c
third_party/heimdal/admin/copy.c
third_party/heimdal/admin/get.c
third_party/heimdal/admin/ktutil-commands.in
third_party/heimdal/admin/ktutil.1
third_party/heimdal/admin/list.c
third_party/heimdal/apply_heimdal.sh
third_party/heimdal/configure.ac
third_party/heimdal/doc/Makefile.am
third_party/heimdal/doc/NTMakefile
third_party/heimdal/doc/apps.texi
third_party/heimdal/doc/copyright.texi
third_party/heimdal/doc/heimdal.texi
third_party/heimdal/doc/hx509.texi
third_party/heimdal/doc/kerberos4.texi [deleted file]
third_party/heimdal/doc/migration.texi
third_party/heimdal/doc/misc.texi
third_party/heimdal/doc/setup.texi
third_party/heimdal/doc/whatis.texi
third_party/heimdal/doc/win2k.texi
third_party/heimdal/include/config.h.w32
third_party/heimdal/kadmin/NTMakefile
third_party/heimdal/kadmin/check.c
third_party/heimdal/kadmin/cpw.c
third_party/heimdal/kadmin/del.c
third_party/heimdal/kadmin/ext.c
third_party/heimdal/kadmin/get.c
third_party/heimdal/kadmin/kadmin-commands.in
third_party/heimdal/kadmin/kadmin.1
third_party/heimdal/kadmin/kadmin_locl.h
third_party/heimdal/kadmin/kadmind.c
third_party/heimdal/kadmin/mod.c
third_party/heimdal/kadmin/rpc.c
third_party/heimdal/kadmin/server.c
third_party/heimdal/kadmin/util.c
third_party/heimdal/kcm/config.c
third_party/heimdal/kcm/events.c
third_party/heimdal/kcm/kcm_locl.h
third_party/heimdal/kdc/Makefile.am
third_party/heimdal/kdc/bx509d.8
third_party/heimdal/kdc/bx509d.c
third_party/heimdal/kdc/digest-service.c
third_party/heimdal/kdc/digest.c
third_party/heimdal/kdc/httpkadmind.8
third_party/heimdal/kdc/httpkadmind.c
third_party/heimdal/kdc/process.c
third_party/heimdal/kdc/simple_csr_authorizer.c
third_party/heimdal/kuser/Makefile.am
third_party/heimdal/kuser/kinit.c
third_party/heimdal/kuser/klist.c
third_party/heimdal/lib/asn1/asn1_compile.1
third_party/heimdal/lib/asn1/gen_copy.c
third_party/heimdal/lib/asn1/gen_free.c
third_party/heimdal/lib/asn1/main.c
third_party/heimdal/lib/asn1/template.c
third_party/heimdal/lib/base/heimbase.h
third_party/heimdal/lib/base/heimbasepriv.h
third_party/heimdal/lib/base/json.c
third_party/heimdal/lib/base/log.c
third_party/heimdal/lib/base/string.c
third_party/heimdal/lib/base/test_base.c
third_party/heimdal/lib/base/version-script.map
third_party/heimdal/lib/gssapi/Makefile.am
third_party/heimdal/lib/gssapi/gss-token.c
third_party/heimdal/lib/gssapi/krb5/8003.c
third_party/heimdal/lib/gssapi/krb5/init_sec_context.c
third_party/heimdal/lib/gssapi/netlogon/crypto.c
third_party/heimdal/lib/gssapi/ntlm/crypto.c
third_party/heimdal/lib/hcrypto/des.c
third_party/heimdal/lib/hcrypto/dh.c
third_party/heimdal/lib/hcrypto/dsa.c
third_party/heimdal/lib/hcrypto/engine.c
third_party/heimdal/lib/hcrypto/evp-openssl.c
third_party/heimdal/lib/hcrypto/evp.c
third_party/heimdal/lib/hcrypto/hmac.c
third_party/heimdal/lib/hcrypto/md2.c
third_party/heimdal/lib/hcrypto/passwd_dlg.c
third_party/heimdal/lib/hcrypto/rand-fortuna.c
third_party/heimdal/lib/hcrypto/rc2.c
third_party/heimdal/lib/hcrypto/rsa.c
third_party/heimdal/lib/hdb/Makefile.am
third_party/heimdal/lib/hdb/common.c
third_party/heimdal/lib/hdb/hdb-mdb.c
third_party/heimdal/lib/hdb/hdb.asn1
third_party/heimdal/lib/hdb/hdb.opt
third_party/heimdal/lib/hdb/keytab.c
third_party/heimdal/lib/hx509/cert.c
third_party/heimdal/lib/hx509/cms.c
third_party/heimdal/lib/hx509/file.c
third_party/heimdal/lib/hx509/hxtool.1 [new file with mode: 0644]
third_party/heimdal/lib/hx509/hxtool.c
third_party/heimdal/lib/hx509/req.c
third_party/heimdal/lib/ipc/server.c
third_party/heimdal/lib/kadm5/ad.c
third_party/heimdal/lib/kadm5/common_glue.c
third_party/heimdal/lib/kadm5/context_s.c
third_party/heimdal/lib/kadm5/create_s.c
third_party/heimdal/lib/kadm5/destroy_s.c
third_party/heimdal/lib/kadm5/get_c.c
third_party/heimdal/lib/kadm5/get_princs_c.c
third_party/heimdal/lib/kadm5/get_princs_s.c
third_party/heimdal/lib/kadm5/init_c.c
third_party/heimdal/lib/kadm5/init_s.c
third_party/heimdal/lib/kadm5/iprop.8
third_party/heimdal/lib/kadm5/ipropd_slave.c
third_party/heimdal/lib/kadm5/libkadm5srv-exports.def
third_party/heimdal/lib/kadm5/private.h
third_party/heimdal/lib/kadm5/version-script-client.map
third_party/heimdal/lib/kadm5/version-script.map
third_party/heimdal/lib/kafs/kafs_locl.h
third_party/heimdal/lib/kafs/rxkad_kdf.c
third_party/heimdal/lib/krb5/Makefile.am
third_party/heimdal/lib/krb5/NTMakefile
third_party/heimdal/lib/krb5/aname_to_localname.c
third_party/heimdal/lib/krb5/changepw.c
third_party/heimdal/lib/krb5/context.c
third_party/heimdal/lib/krb5/convert_creds.c
third_party/heimdal/lib/krb5/kcm.c
third_party/heimdal/lib/krb5/keytab.c
third_party/heimdal/lib/krb5/krb5-v4compat.h [deleted file]
third_party/heimdal/lib/krb5/pac.c
third_party/heimdal/lib/krb5/pkinit.c
third_party/heimdal/lib/krb5/send_to_kdc.c
third_party/heimdal/lib/krb5/store.c
third_party/heimdal/lib/libedit/config.h.in
third_party/heimdal/lib/ntlm/digest.c
third_party/heimdal/lib/ntlm/ntlm.c
third_party/heimdal/lib/otp/otp_verify.c
third_party/heimdal/lib/roken/Makefile.am
third_party/heimdal/lib/roken/base32.c
third_party/heimdal/lib/roken/dirent-test.c
third_party/heimdal/lib/roken/fnmatch.c
third_party/heimdal/lib/roken/getaddrinfo.c
third_party/heimdal/lib/roken/getuserinfo.c
third_party/heimdal/lib/roken/parse_units.c
third_party/heimdal/lib/roken/parse_units.h
third_party/heimdal/lib/roken/snprintf.c
third_party/heimdal/lib/roken/strftime.c
third_party/heimdal/lib/roken/strptime.c
third_party/heimdal/lib/sl/slc-gram.y
third_party/heimdal/lib/wind/utf8.c
third_party/heimdal/packages/windows/sdk/NTMakefile
third_party/heimdal/tests/gss/krb5.conf.in
third_party/heimdal/tests/kdc/check-bx509.in
third_party/heimdal/tests/kdc/check-httpkadmind.in
third_party/heimdal/tests/kdc/check-kadmin.in
third_party/heimdal/tests/kdc/check-kdc.in
third_party/heimdal/tests/kdc/check-referral.in
third_party/heimdal/tests/kdc/krb5-httpkadmind.conf.in
third_party/heimdal/tests/kdc/krb5.conf.in
third_party/heimdal/windows/README.md

index 342f850f1c7005c8511bfa68d90cabdbe5baa5ff..3463e99b6e97cafc2f44ecdcfe840644d0b432df 100644 (file)
@@ -66,7 +66,7 @@ jobs:
                 echo "bison, flex, ncurses, texinfo, and unzip are in the base OS."
                 echo "berkeley-db, perl, python, curl, and jq are installed in the"
                 echo "base image already."
-                brew install autoconf automake libtool cpanm
+                brew install autoconf automake libtool cpanm texinfo texi2html
                 sudo cpanm install JSON
             - name: Clone repository
               uses: actions/checkout@v1
@@ -79,8 +79,10 @@ jobs:
                 /bin/sh ./autogen.sh
                 mkdir build
                 cd build
-                ../configure --srcdir=`dirname "$PWD"` --disable-afs-support --enable-maintainer-mode --enable-developer $CONFIGURE_OPTS --prefix=$HOME/inst CFLAGS="-Wno-error=shadow -Wno-error=bad-function-cast -Wno-error=unused-function -Wno-error=unused-result -Wno-error=deprecated-declarations" CFLAGS="-O0 -g -ggdb3"
+                ../configure --srcdir=`dirname "$PWD"` --disable-heimdal-documentation --disable-afs-support --enable-maintainer-mode --enable-developer $CONFIGURE_OPTS --prefix=$HOME/inst CFLAGS="-Wno-error=shadow -Wno-error=bad-function-cast -Wno-error=unused-function -Wno-error=unused-result -Wno-error=deprecated-declarations" CFLAGS="-O0 -g -ggdb3"
                 ulimit -c unlimited
+                PATH=/usr/local/opt/texinfo/bin:$PATH
+                export PATH
                 make -j4
             #- name: Setup upterm session
             #  uses: lhotari/action-upterm@v1
index f1c187c397a9018d03efcc8b2b4ad24579a6fa0c..0d3bad83b216209bfa1ccaad0e1184570d2b0f7b 100644 (file)
@@ -4,6 +4,7 @@ on:
     push:
       branches:
          - 'master'
+         - 'windows-build'
          - 'heimdal-7-1-branch'
       paths:
          - '!docs/**'
@@ -76,6 +77,7 @@ jobs:
                 pacman --noconfirm -S bison
                 pacman --noconfirm -S perl
                 pacman --noconfirm -S perl-JSON
+                pacman --noconfirm -S texinfo
                 set PATH=%PATH%;%wix%bin
                 title Heimdal Build %CPU% %dbg__type%
                 set "PATH=%PATH%;C:\Perl64\bin;C:\tools\cygwin\bin;C:\Program Files (x86)\HTML Help Workshop"
index a4a7bb4c0f911e478760fefaac1b6ea6a92e385a..1821d4b2e4bb02abb31cf521b3e9007480e99159 100644 (file)
@@ -37,6 +37,7 @@ LDADD = \
        $(LIB_hcrypto) \
        $(top_builddir)/lib/asn1/libasn1.la \
        $(top_builddir)/lib/sl/libsl.la \
+       $(LIB_heimbase) \
        $(LIB_readline) \
        $(LIB_roken)
 
index 13580b9bb570a3d1586627b84f157975959b259c..5f1920ff8be2e89bbe3ae06c8ce42d1cd35d2433 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997-2005 Kungliga Tekniska Högskolan
+ * Copyright (c) 1997-2022 Kungliga Tekniska Högskolan
  * (Royal Institute of Technology, Stockholm, Sweden).
  * All rights reserved.
  *
@@ -32,6 +32,8 @@
  */
 
 #include "ktutil_locl.h"
+#include <heimbase.h>
+#include <base64.h>
 
 RCSID("$Id$");
 
@@ -153,6 +155,178 @@ kt_add(struct add_options *opt, int argc, char **argv)
        krb5_warn(context, ret, "add");
  out:
     krb5_kt_free_entry(context, &entry);
-    krb5_kt_close(context, keytab);
+    if (ret == 0) {
+        ret = krb5_kt_close(context, keytab);
+        if (ret)
+            krb5_warn(context, ret, "Could not write the keytab");
+    } else {
+        krb5_kt_close(context, keytab);
+    }
+    return ret != 0;
+}
+
+/* We might be reading from a pipe, so we can't use rk_undumpdata() */
+static char *
+read_file(FILE *f)
+{
+    size_t alloced;
+    size_t len = 0;
+    size_t bytes;
+    char *res, *end, *p;
+
+    if ((res = malloc(1024)) == NULL)
+        err(1, "Out of memory");
+    alloced = 1024;
+
+    end = res + alloced;
+    p = res;
+    do {
+        if (p == end) {
+            char *tmp;
+
+            if ((tmp = realloc(res, alloced + (alloced > 1))) == NULL)
+                err(1, "Out of memory");
+            alloced += alloced > 1;
+            p = tmp + (p - res);
+            res = tmp;
+            end = res + alloced;
+        }
+        bytes = fread(p, 1, end - p, f);
+        len += bytes;
+        p += bytes;
+    } while (bytes && !feof(f) && !ferror(f));
+
+    if (ferror(f))
+        errx(1, "Could not read all input");
+    if (p == end) {
+        char *tmp;
+
+        if ((tmp = strndup(res, len)) == NULL)
+            err(1, "Out of memory");
+        free(res);
+        res = tmp;
+    }
+    if (strlen(res) != len)
+        err(1, "Embedded NULs in input!");
+    return res;
+}
+
+static void
+json2keytab_entry(heim_dict_t d, krb5_keytab kt, size_t idx)
+{
+    krb5_keytab_entry e;
+    krb5_error_code ret;
+    heim_object_t v;
+    uint64_t u;
+    int64_t i;
+    char *buf = NULL;
+
+    memset(&e, 0, sizeof(e));
+
+    v = heim_dict_get_value(d, HSTR("timestamp"));
+    if (heim_get_tid(v) != HEIM_TID_NUMBER)
+        goto bad;
+    u = heim_number_get_long(v);
+    e.timestamp = u;
+    if (u != (uint64_t)e.timestamp)
+        goto bad;
+
+    v = heim_dict_get_value(d, HSTR("kvno"));
+    if (heim_get_tid(v) != HEIM_TID_NUMBER)
+        goto bad;
+    i = heim_number_get_long(v);
+    e.vno = i;
+    if (i != (int64_t)e.vno)
+        goto bad;
+
+    v = heim_dict_get_value(d, HSTR("enctype_number"));
+    if (heim_get_tid(v) != HEIM_TID_NUMBER)
+        goto bad;
+    i = heim_number_get_long(v);
+    e.keyblock.keytype = i;
+    if (i != (int64_t)e.keyblock.keytype)
+        goto bad;
+
+    v = heim_dict_get_value(d, HSTR("key"));
+    if (heim_get_tid(v) != HEIM_TID_STRING)
+        goto bad;
+    {
+        const char *s = heim_string_get_utf8(v);
+        int declen;
+
+        if ((buf = malloc(strlen(s))) == NULL)
+            err(1, "Out of memory");
+        declen = rk_base64_decode(s, buf);
+        if (declen < 0)
+            goto bad;
+        e.keyblock.keyvalue.data = buf;
+        e.keyblock.keyvalue.length = declen;
+    }
+
+    v = heim_dict_get_value(d, HSTR("principal"));
+    if (heim_get_tid(v) != HEIM_TID_STRING)
+        goto bad;
+    ret = krb5_parse_name(context, heim_string_get_utf8(v), &e.principal);
+    if (ret == 0)
+        ret = krb5_kt_add_entry(context, kt, &e);
+
+    /* For now, ignore aliases; besides, they're never set anywhere in-tree */
+
+    if (ret)
+        krb5_warn(context, ret,
+                  "Could not parse or write keytab entry %lu",
+                  (unsigned long)idx);
+bad:
+    krb5_free_principal(context, e.principal);
+}
+
+int
+kt_import(void *opt, int argc, char **argv)
+{
+    krb5_error_code ret;
+    krb5_keytab kt;
+    heim_object_t o;
+    heim_error_t json_err = NULL;
+    heim_json_flags_t flags = HEIM_JSON_F_STRICT;
+    FILE *f = argc == 0 ? stdin : fopen(argv[0], "r");
+    size_t alen, i;
+    char *json;
+
+    if (f == NULL)
+        err(1, "Could not open file %s", argv[0]);
+
+    json = read_file(f);
+    o = heim_json_create(json, 10, flags, &json_err);
+    free(json);
+    if (o == NULL) {
+        if (json_err != NULL) {
+            o = heim_error_copy_string(json_err);
+            if (o)
+                errx(1, "Could not parse JSON: %s", heim_string_get_utf8(o));
+        }
+        errx(1, "Could not parse JSON");
+    }
+
+    if (heim_get_tid(o) != HEIM_TID_ARRAY)
+        errx(1, "JSON text must be an array");
+
+    alen = heim_array_get_length(o);
+    if (alen == 0)
+        errx(1, "Empty JSON array; not overwriting keytab");
+
+    if ((kt = ktutil_open_keytab()) == NULL)
+       err(1, "Could not open keytab");
+
+    for (i = 0; i < alen; i++) {
+        heim_object_t e = heim_array_get_value(o, i);
+
+        if (heim_get_tid(e) != HEIM_TID_DICT)
+            warnx("Element %ld of JSON text array is not an object", (long)i);
+        else
+            json2keytab_entry(heim_array_get_value(o, i), kt, i);
+    }
+    ret = krb5_kt_close(context, kt);
+    if (ret)
+        krb5_warn(context, ret, "Could not write the keytab");
     return ret != 0;
 }
index 7b50de1c3cb22f05757974eab4fde310a19fc0e2..8acd6e48ed085558c63d4a52481610cd0c2f4e8a 100644 (file)
@@ -47,7 +47,7 @@ compare_keyblock(const krb5_keyblock *a, const krb5_keyblock *b)
 }
 
 int
-kt_copy (void *opt, int argc, char **argv)
+kt_copy (struct copy_options *opt, int argc, char **argv)
 {
     krb5_error_code ret;
     krb5_keytab src_keytab, dst_keytab;
@@ -106,11 +106,18 @@ kt_copy (void *opt, int argc, char **argv)
                           "already exists for %s, keytype %s, kvno %d",
                           name_str, etype_str, entry.vno);
            }
-           krb5_kt_free_entry(context, &dummy);
-           krb5_kt_free_entry (context, &entry);
-           free(name_str);
-           free(etype_str);
-           continue;
+            if (!opt->copy_duplicates_flag) {
+                krb5_kt_free_entry(context, &dummy);
+                krb5_kt_free_entry (context, &entry);
+                free(name_str);
+                free(etype_str);
+                continue;
+            }
+            /*
+             * Because we can end up trying all keys that match the enctype,
+             * copying entries with duplicate principal, vno, and enctype, but
+             * different keys, can be useful.
+             */
        } else if(ret != KRB5_KT_NOTFOUND) {
            krb5_warn (context, ret, "%s: fetching %s/%s/%u",
                       to, name_str, etype_str, entry.vno);
index f56e50f43599cbc0d25fd95fba2544fa667eba32..ecd6f6a160e3d2a600b0f31279407959ea5a8d47 100644 (file)
@@ -197,23 +197,27 @@ kt_get(struct get_options *opt, int argc, char **argv)
                break;
        }
 
-       ret = kadm5_create_principal(kadm_handle, &princ, mask, "thisIs_aUseless.password123");
-       if(ret == 0)
-           created = 1;
-       else if(ret != KADM5_DUP) {
-           krb5_warn(context, ret, "kadm5_create_principal(%s)", argv[a]);
-           krb5_free_principal(context, princ_ent);
-           failed++;
-           continue;
-       }
-        ret = kadm5_randkey_principal_3(kadm_handle, princ_ent, keep, nks, ks,
-                                        &keys, &n_keys);
-       if (ret) {
-           krb5_warn(context, ret, "kadm5_randkey_principal(%s)", argv[a]);
-           krb5_free_principal(context, princ_ent);
-           failed++;
-           continue;
-       }
+        if (opt->create_flag) {
+            ret = kadm5_create_principal(kadm_handle, &princ, mask, "thisIs_aUseless.password123");
+            if(ret == 0)
+                created = 1;
+            else if(ret != KADM5_DUP) {
+                krb5_warn(context, ret, "kadm5_create_principal(%s)", argv[a]);
+                krb5_free_principal(context, princ_ent);
+                failed++;
+                continue;
+            }
+        }
+        if (opt->change_keys_flag) {
+            ret = kadm5_randkey_principal_3(kadm_handle, princ_ent, keep, nks, ks,
+                                            &keys, &n_keys);
+            if (ret) {
+                krb5_warn(context, ret, "kadm5_randkey_principal(%s)", argv[a]);
+                krb5_free_principal(context, princ_ent);
+                failed++;
+                continue;
+            }
+        }
 
        ret = kadm5_get_principal(kadm_handle, princ_ent, &princ,
                              KADM5_PRINCIPAL | KADM5_KVNO | KADM5_ATTRIBUTES);
index 2b771e931a116ddb615d8509eb2f82fdf81b3e2f..a85eb5c5715bc427ff075526fd3b44187cd95e98 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2004 Kungliga Tekniska Högskolan
+ * Copyright (c) 2004-2022 Kungliga Tekniska Högskolan
  * (Royal Institute of Technology, Stockholm, Sweden). 
  * All rights reserved. 
  *
@@ -151,11 +151,17 @@ command = {
 }
 command = {
        name = "copy"
+       name = "merge"
        function = "kt_copy"
+       option = {
+               long = "copy-duplicates"
+               type = "flag"
+               help = "copy entries for the same principal and kvno, but different keys"
+       }
        argument = "source destination"
        min_args = "2"
        max_args = "2"
-       help = "Copies one keytab to another."
+       help = "Merges one keytab into another."
 }
 command = {
        name = "get"
@@ -166,6 +172,16 @@ command = {
                help = "admin principal"
                argument = "principal"
        }
+       option = {
+               long = "create"
+               type = "-flag"
+               help = "do not create the principal"
+       }
+       option = {
+               long = "change-keys"
+               type = "-flag"
+               help = "do not change the principal's keys"
+       }
        option = {
                long = "enctypes"
                short = "e"
@@ -214,6 +230,14 @@ command = {
        argument = "principal..."
        help = "Change keys for specified principals, and add them to the keytab."
 }
+command = {
+       name = "import"
+       function = "kt_import"
+       help = "Imports a keytab from JSON output of ktutil list --json --keys."
+        min_args = "0"
+        max_args = "1"
+       argument = "JSON-FILE"
+}
 command = {
        name = "list"
        option = {
@@ -226,6 +250,11 @@ command = {
                type = "flag"
                help = "show timestamps"
        }
+       option = {
+               long = "json"
+               type = "flag"
+               help = "output JSON representation"
+       }
        max_args = "0"
        function = "kt_list"
        help = "Show contents of keytab."
index 125b5e8f0d5eb61676faee5603195f4af2796aff..0036edcbd9be5813ed0dc802d4deeb0d04ef8cb9 100644 (file)
@@ -60,7 +60,7 @@ Verbose output.
 .Ar command
 can be one of the following:
 .Bl -tag -width srvconvert
-.It add Oo Fl p Ar principal Oc Oo Fl Fl principal= Ns Ar principal Oc \
+.It Nm add Oo Fl p Ar principal Oc Oo Fl Fl principal= Ns Ar principal Oc \
 Oo Fl V Ar kvno Oc Oo Fl Fl kvno= Ns Ar kvno Oc Oo Fl e Ar enctype Oc \
 Oo Fl Fl keepold | Fl Fl keepallold | Fl Fl pruneall Oc \
 Oo Fl Fl enctype= Ns Ar enctype Oc Oo Fl w Ar password Oc \
@@ -72,7 +72,7 @@ principal to add; if what you really want is to add a new principal to
 the keytab, you should consider the
 .Ar get
 command, which talks to the kadmin server.
-.It change Oo Fl r Ar realm Oc Oo Fl Fl realm= Ns Ar realm Oc \
+.It Nm change Oo Fl r Ar realm Oc Oo Fl Fl realm= Ns Ar realm Oc \
 Oo Fl Fl keepold | Fl Fl keepallold | Fl Fl pruneall Oc \
 Oo Fl Fl enctype= Ns Ar enctype Oc \
 Oo Fl Fl a Ar host Oc Oo Fl Fl admin-server= Ns Ar host Oc \
@@ -82,30 +82,68 @@ server for the realm of a keytab entry.  Otherwise it will use the
 values specified by the options.
 .Pp
 If no principals are given, all the ones in the keytab are updated.
-.It copy Ar keytab-src Ar keytab-dest
+.It Nm copy Oo Fl Fl copy-duplicates Oc Ar keytab-src Ar keytab-dest
 Copies all the entries from
 .Ar keytab-src
 to
 .Ar keytab-dest .
-.It get Oo Fl p Ar admin principal Oc \
+Because entries already in
+.Ar keytab-dest
+are kept, this command functions to merge keytabs.
+Entries for the same principal, key version number, and
+encryption type in the
+.Ar keytab-src
+that are also in the
+.Ar keytab-dest
+will not be copied to the
+.Ar keytab-dest
+unless the
+.Fl Fl copy-duplicates
+option is given.
+.It Nm get Oo Fl p Ar admin principal Oc \
 Oo Fl Fl principal= Ns Ar admin principal Oc Oo Fl e Ar enctype Oc \
+Oo Fl Fl no-create Oc \
+Oo Fl Fl no-change-keys Oc \
 Oo Fl Fl keepold | Fl Fl keepallold | Fl Fl pruneall Oc \
 Oo Fl Fl enctypes= Ns Ar enctype Oc Oo Fl r Ar realm Oc \
 Oo Fl Fl realm= Ns Ar realm Oc Oo Fl a Ar admin server Oc \
 Oo Fl Fl admin-server= Ns Ar admin server Oc Oo Fl s Ar server port Oc \
 Oo Fl Fl server-port= Ns Ar server port Oc Ar principal ...
+.Pp
 For each
 .Ar principal ,
-generate a new key for it (creating it if it doesn't already exist),
-and put that key in the keytab.
+get a the principal's keys from the KDC via the kadmin protocol,
+creating the principal if it doesn't exist (unless
+.Fl Fl no-create
+is given), and changing its keys to new random keys (unless
+.Fl Fl no-change-keys
+is given).
 .Pp
 If no
 .Ar realm
 is specified, the realm to operate on is taken from the first
 principal.
-.It list Oo Fl Fl keys Oc Op Fl Fl timestamp
+.It Nm import Oo JSON-FILE Oc
+Read an array of keytab entries in a JSON file and copy them to
+the keytab.
+Use the
+.Nm list
+command with its
+.Fl Fl json
+option
+and
+.Fl Fl keys
+option to export a keytab.
+.It Nm list Oo Fl Fl keys Oc Op Fl Fl timestamp Oo Op Fl Fl json Oc
 List the keys stored in the keytab.
-.It remove Oo Fl p Ar principal Oc Oo Fl Fl principal= Ns Ar principal Oc \
+Use the
+.Fl Fl json
+and
+.Fl Fl keys
+options to export a keytab as JSON for importing with the
+.Nm import
+command.
+.It Nm remove Oo Fl p Ar principal Oc Oo Fl Fl principal= Ns Ar principal Oc \
 Oo Fl V kvno Oc Oo Fl Fl kvno= Ns Ar kvno Oc Oo Fl e enctype Oc \
 Oo Fl Fl enctype= Ns Ar enctype Oc
 Removes the specified key or keys. Not specifying a
@@ -113,16 +151,28 @@ Removes the specified key or keys. Not specifying a
 removes keys with any version number. Not specifying an
 .Ar enctype
 removes keys of any type.
-.It rename Ar from-principal Ar to-principal
-Renames all entries in the keytab that match the
+.It Nm merge Oo Fl Fl copy-duplicates Oc Ar keytab-src Ar keytab-dest
+An alias for the
+.Nm copy
+command.
+.It Nm rename Ar from-principal Ar to-principal
+Renames all entries for the
+.Ar from-principal
+in the keytab
 .Ar from-principal
 to
 .Ar to-principal .
-.It purge Op Fl Fl age= Ns Ar age
+.It Nm purge Op Fl Fl age= Ns Ar age
 Removes all old versions of a key for which there is a newer version
 that is at least
 .Ar age
 (default one week) old.
+Note that this does not update the KDC database.
+The
+.Xr kadmin 1
+command has a
+.Nm prune
+command that can do this on the KDC side.
 .El
 .Sh SEE ALSO
 .Xr kadmin 1
index 31be546111572a2e6da3b1401b92b22e430a8760..9d1e9d5d483712b7bb520d694a64d00ab2c82580 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997-2004 Kungliga Tekniska Högskolan
+ * Copyright (c) 1997-2022 Kungliga Tekniska Högskolan
  * (Royal Institute of Technology, Stockholm, Sweden).
  * All rights reserved.
  *
@@ -32,6 +32,7 @@
  */
 
 #include "ktutil_locl.h"
+#include <heimbase.h>
 #include <rtbl.h>
 
 RCSID("$Id$");
@@ -131,7 +132,8 @@ do_list(struct list_options *opt, const char *keytab_str)
            struct rk_strpool *p = NULL;
 
            for (i = 0; i< entry.aliases->len; i++) {
-               krb5_unparse_name_fixed(context, entry.principal, buf, sizeof(buf));
+               krb5_unparse_name_fixed(context, &entry.aliases->val[i],
+                                        buf, sizeof(buf));
                p = rk_strpoolprintf(p, "%s%s", buf,
                                      i + 1 < entry.aliases->len ? ", " : "");
 
@@ -152,6 +154,137 @@ out:
     return ret;
 }
 
+static int
+do_list1_json(struct list_options *opt,
+              const char *keytab_str,
+              heim_array_t a)
+{
+    krb5_error_code ret;
+    krb5_keytab keytab;
+    krb5_keytab_entry entry;
+    krb5_kt_cursor cursor;
+
+    ret = krb5_kt_resolve(context, keytab_str, &keytab);
+    if (ret) {
+       krb5_warn(context, ret, "resolving keytab %s", keytab_str);
+       return ret;
+    }
+
+    ret = krb5_kt_start_seq_get(context, keytab, &cursor);
+    if(ret) {
+       krb5_warn(context, ret, "krb5_kt_start_seq_get %s", keytab_str);
+       krb5_kt_close(context, keytab);
+       return ret;
+    }
+
+    //if (opt->timestamp_flag)
+    //if (opt->keys_flag)
+
+    while (krb5_kt_next_entry(context, keytab, &entry, &cursor) == 0) {
+        heim_dict_t d = heim_dict_create(5);
+        heim_object_t o;
+       char *s;
+
+        heim_array_append_value(a, d);
+        heim_dict_set_value(d, HSTR("keytab"),
+                            o = heim_string_create(keytab_str)); heim_release(o);
+        heim_dict_set_value(d, HSTR("kvno"), o = heim_number_create(entry.vno));
+        heim_release(o);
+        heim_dict_set_value(d, HSTR("enctype_number"),
+                            o = heim_number_create(entry.keyblock.keytype));
+        heim_release(o);
+        heim_dict_set_value(d, HSTR("flags"),
+                            o = heim_number_create(entry.flags));
+        heim_release(o);
+       ret = krb5_enctype_to_string(context, entry.keyblock.keytype, &s);
+       if (ret == 0) {
+            heim_dict_set_value(d, HSTR("enctype"), o = heim_string_create(s));
+            heim_release(o);
+            free(s);
+       }
+        heim_dict_set_value(d, HSTR("timestamp"),
+                            o = heim_number_create(entry.timestamp));
+        heim_release(o);
+
+       ret = krb5_unparse_name(context, entry.principal, &s);
+        if (ret)
+            krb5_err(context, 1, ret, "Could not format principal");
+        heim_dict_set_value(d, HSTR("principal"), o = heim_string_create(s));
+        heim_release(o);
+        free(s);
+
+       if (opt->keys_flag) {
+            o = heim_data_create(entry.keyblock.keyvalue.data,
+                                 entry.keyblock.keyvalue.length);
+            heim_dict_set_value(d, HSTR("key"), o);
+            heim_release(o);
+       }
+       if (entry.aliases) {
+            heim_array_t aliases = heim_array_create();
+           unsigned int i;
+
+           for (i = 0; i< entry.aliases->len; i++) {
+               ret = krb5_unparse_name(context, &entry.aliases->val[i], &s);
+                if (ret)
+                    krb5_err(context, 1, ret, "Could not format principal");
+                heim_array_append_value(aliases, o = heim_string_create(s));
+                heim_release(o);
+                free(s);
+           }
+            heim_dict_set_value(d, HSTR("aliases"), aliases);
+            heim_release(aliases);
+            free(s);
+       }
+
+       krb5_kt_free_entry(context, &entry);
+        heim_release(d);
+    }
+
+    ret = krb5_kt_end_seq_get(context, keytab, &cursor);
+    krb5_kt_close(context, keytab);
+    return ret;
+}
+
+static int
+do_list_json(struct list_options *opt, const char *keytab_str)
+{
+    krb5_error_code ret = 0;
+    heim_json_flags_t flags =
+        (HEIM_JSON_F_STRICT | HEIM_JSON_F_INDENT2 | HEIM_JSON_F_NO_DATA_DICT) &
+        ~HEIM_JSON_F_NO_DATA;
+    heim_array_t a = heim_array_create();
+    heim_string_t s;
+
+    /*
+     * Special-case the ANY: keytab type.  What do we get from this?  We get to
+     * include the actual keytab name for each entry in its JSON
+     * representation.  Otherwise there would be no point because the ANY:
+     * keytab type iterates all the keytabs it joins.
+     *
+     * Why strncasecmp() though?  Because do_list() uses it, though it arguably
+     * never should have.
+     */
+    if (strncasecmp(keytab_str, "ANY:", 4) == 0) {
+       char buf[1024];
+
+       keytab_str += 4;
+       ret = 0;
+       while (strsep_copy((const char**)&keytab_str, ",",
+                          buf, sizeof(buf)) != -1) {
+           if (do_list1_json(opt, buf, a))
+               ret = 1;
+       }
+    } else {
+        ret = do_list1_json(opt, keytab_str, a);
+    }
+
+    s = heim_json_copy_serialize(a, flags, NULL);
+    printf("%s", heim_string_get_utf8(s));
+    heim_release(a);
+    heim_release(s);
+    return ret;
+}
+
 int
 kt_list(struct list_options *opt, int argc, char **argv)
 {
@@ -168,5 +301,7 @@ kt_list(struct list_options *opt, int argc, char **argv)
        }
        keytab_string = kt;
     }
+    if (opt->json_flag)
+        return do_list_json(opt, keytab_string) != 0;
     return do_list(opt, keytab_string) != 0;
 }
index fc9aa292e5a0d8473f5cd239d642a3b869d2127c..a720766d3c8624d3a7d429d8809f67f9676ab2e0 100755 (executable)
@@ -48,8 +48,8 @@ apply () {
 
 try_patch() {
     commit="$1"
-    git format-patch --stdout $commit -1 source4/heimdal > "$commit".patch
-    sed -i 's|/source4/heimdal/|/|g' "$commit".patch
+    git format-patch --stdout $commit -1 third_party/heimdal > "$commit".patch
+    sed -i 's|/third_party/heimdal/|/|g' "$commit".patch
     sed -i "s|^---$|(cherry picked from Samba commit $commit)\n---|g" "$commit".patch
     pushd $LORIKEET_PATH || exit 1
     git reset --hard
@@ -68,7 +68,7 @@ try_patch() {
     popd || exit 1
 }
 
-commits="$(git log --pretty=oneline --reverse $IMPORT_HASH..HEAD -- source4/heimdal | cut -d' ' -f1)"
+commits="$(git log --pretty=oneline --reverse $IMPORT_HASH..HEAD -- third_party/heimdal | cut -d' ' -f1)"
 for c in $commits; do
     git log $c -1
     echo -n "Try apply? [Y/n] "
index 8c0b746ba5ce61e95df2c09a1d16b9ae1e62f033..b946dfff4c1cee85d8aa63b8c73f1826473695cc 100644 (file)
@@ -505,16 +505,20 @@ rk_WIN32_EXPORT(BUILD_ROKEN_LIB, ROKEN_LIB)
 rk_WIN32_EXPORT(BUILD_GSSAPI_LIB, GSSAPI_LIB)
 rk_WIN32_EXPORT(BUILD_KDC_LIB, KDC_LIB)
 
-dnl Deal with switch FALLTHROUGH
+dnl Deal with switch fallthrough warnings
 AH_TOP([
-#if defined(__GNUC__)
-#if __GNUC__ >= 7
-# define fallthrough __attribute__((fallthrough))
+#if defined(DISPATCH_FALLTHROUGH)
+# define HEIM_FALLTHROUGH DISPATCH_FALLTHROUGH
 #else
-# define fallthrough do {} while (0) /* fallthrough */
-#endif
-#else
-# define fallthrough  do {} while (0) /* fallthrough */
+# if defined(__GNUC__)
+#  if __GNUC__ >= 7
+#   define HEIM_FALLTHROUGH __attribute__((fallthrough))
+#  else
+#   define HEIM_FALLTHROUGH do {} while (0) /* fallthrough */
+#  endif
+# else
+#  define HEIM_FALLTHROUGH do {} while (0) /* fallthrough */
+# endif
 #endif
 ])
 
index ed95c305fe35727a87be46f58d0a3e106db89117..aa7f8130f49f91ebfdce5878bcd8674c6faa1a3d 100644 (file)
@@ -10,6 +10,8 @@ TEXI2DVI = true # ARGH, make distcheck can't be disabled to not build dvifiles
 
 info_TEXINFOS = heimdal.texi hx509.texi
 
+BUILT_SOURCES = vars.texi
+
 dxy_subst = sed -e 's,[@]srcdir[@],$(srcdir),g' \
        -e 's,[@]objdir[@],.,g' \
        -e 's,[@]PACKAGE_VERSION[@],$(PACKAGE_VERSION),g'
@@ -119,7 +121,6 @@ heimdal_TEXINFOS = \
        heimdal.texi \
        install.texi \
        intro.texi \
-       kerberos4.texi \
        migration.texi \
        misc.texi \
        programming.texi \
index 0299620ac118ffd9c9231eb19309916e66f4c264..4769c9126bcd7fbd2f8c8a6a47a45d72980c5630 100644 (file)
@@ -40,7 +40,6 @@ heimdal_TEXINFOS = \
        $(OBJ)\heimdal.texi \
        $(OBJ)\install.texi \
        $(OBJ)\intro.texi \
-       $(OBJ)\kerberos4.texi \
        $(OBJ)\migration.texi \
        $(OBJ)\misc.texi \
        $(OBJ)\programming.texi \
index 98585c4d0a7275b7b9082b75899b7ee6cbd41f8e..2b48edaafaff999d165e3419e7f92110b7e37a17 100644 (file)
 @chapter Applications
 
 @menu
-* Authentication modules::
 * AFS::
 @end menu
 
-@node  Authentication modules, AFS, Applications, Applications
-@section Authentication modules
-
-The problem of having different authentication mechanisms has been
-recognised by several vendors, and several solutions have appeared. In
-most cases these solutions involve some kind of shared modules that are
-loaded at run-time.  Modules for some of these systems can be found in
-@file{lib/auth}.  Presently there are modules for Digital's SIA,
-and IRIX' @code{login} and @code{xdm} (in
-@file{lib/auth/afskauthlib}).
-
-@menu
-* Digital SIA::                 
-* IRIX::                        
-@end menu
-
-@node Digital SIA, IRIX, Authentication modules, Authentication modules
-@subsection Digital SIA
-
-How to install the SIA module depends on which OS version you're
-running. Tru64 5.0 has a new command, @file{siacfg}, which makes this
-process quite simple. If you have this program, you should just be able
-to run:
-@example
-siacfg -a KRB5 /usr/athena/lib/libsia_krb5.so
-@end example
-
-On older versions, or if you want to do it by hand, you have to do the
-following (not tested by us on Tru64 5.0):
-
-@itemize @bullet
-
-@item
-Make sure @file{libsia_krb5.so} is available in
-@file{/usr/athena/lib}. If @file{/usr/athena} is not on local disk, you
-might want to put it in @file{/usr/shlib} or someplace else. If you do,
-you'll have to edit @file{krb5_matrix.conf} to reflect the new location
-(you will also have to do this if you installed in some other directory
-than @file{/usr/athena}). If you built with shared libraries, you will
-have to copy the shared @file{libkrb.so}, @file{libdes.so},
-@file{libkadm.so}, and @file{libkafs.so} to a place where the loader can
-find them (such as @file{/usr/shlib}).
-@item
-Copy (your possibly edited) @file{krb5_matrix.conf} to @file{/etc/sia}.
-@item
-Apply @file{security.patch} to @file{/sbin/init.d/security}.
-@item
-Turn on KRB5 security by issuing @kbd{rcmgr set SECURITY KRB5} and
-@kbd{rcmgr set KRB5_MATRIX_CONF krb5_matrix.conf}.
-@item
-Digital thinks you should reboot your machine, but that really shouldn't
-be necessary.  It's usually sufficient just to run
-@kbd{/sbin/init.d/security start} (and restart any applications that use
-SIA, like @code{xdm}.)
-@end itemize
-
-Users with local passwords (like @samp{root}) should be able to login
-safely.
-
-When using Digital's xdm the @samp{KRB5CCNAME} environment variable isn't
-passed along as it should (since xdm zaps the environment). Instead you
-have to set @samp{KRB5CCNAME} to the correct value in
-@file{/usr/lib/X11/xdm/Xsession}. Add a line similar to
-@example
-KRB5CCNAME=FILE:/tmp/krb5cc`id -u`_`ps -o ppid= -p $$`; export KRB5CCNAME
-@end example
-If you use CDE, @code{dtlogin} allows you to specify which additional
-environment variables it should export. To add @samp{KRB5CCNAME} to this
-list, edit @file{/usr/dt/config/Xconfig}, and look for the definition of
-@samp{exportList}. You want to add something like:
-@example
-Dtlogin.exportList:     KRB5CCNAME
-@end example
-
-@subsubheading Notes to users with Enhanced security
-
-Digital's @samp{ENHANCED} (C2) security, and Kerberos solve two
-different problems. C2 deals with local security, adds better control of
-who can do what, auditing, and similar things. Kerberos deals with
-network security.
-
-To make C2 security work with Kerberos you will have to do the
-following.
-
-@itemize @bullet
-@item
-Replace all occurrences of @file{krb5_matrix.conf} with
-@file{krb5+c2_matrix.conf} in the directions above.
-@item
-You must enable ``vouching'' in the @samp{default} database.  This will
-make the OSFC2 module trust other SIA modules, so you can login without
-giving your C2 password. To do this use @samp{edauth} to edit the
-default entry @kbd{/usr/tcb/bin/edauth -dd default}, and add a
-@samp{d_accept_alternate_vouching} capability, if not already present.
-@item
-For each user who does @emph{not} have a local C2 password, you should
-set the password expiration field to zero. You can do this for each
-user, or in the @samp{default} table. To do this use @samp{edauth} to
-set (or change) the @samp{u_exp} capability to @samp{u_exp#0}.
-@item
-You also need to be aware that the shipped @file{login}, @file{rcp}, and
-@file{rshd}, don't do any particular C2 magic (such as checking for
-various forms of disabled accounts), so if you rely on those features,
-you shouldn't use those programs. If you configure with
-@samp{--enable-osfc2}, these programs will, however, set the login
-UID. Still: use at your own risk.
-@end itemize
-
-At present @samp{su} does not accept the vouching flag, so it will not
-work as expected.
-
-Also, kerberised ftp will not work with C2 passwords. You can solve this
-by using both Digital's ftpd and our on different ports.
-
-@strong{Remember}, if you do these changes you will get a system that
-most certainly does @emph{not} fulfil the requirements of a C2
-system. If C2 is what you want, for instance if someone else is forcing
-you to use it, you're out of luck.  If you use enhanced security because
-you want a system that is more secure than it would otherwise be, you
-probably got an even more secure system. Passwords will not be sent in
-the clear, for instance.
-
-@node IRIX, , Digital SIA, Authentication modules
-@subsection IRIX
-
-The IRIX support is a module that is compatible with Transarc's
-@file{afskauthlib.so}.  It should work with all programs that use this
-library. This should include @command{login} and @command{xdm}.
-
-The interface is not very documented but it seems that you have to copy
-@file{libkafs.so}, @file{libkrb.so}, and @file{libdes.so} to
-@file{/usr/lib}, or build your @file{afskauthlib.so} statically.
-
-The @file{afskauthlib.so} itself is able to reside in
-@file{/usr/vice/etc}, @file{/usr/afsws/lib}, or the current directory
-(wherever that is).
-
-IRIX 6.4 and newer seem to have all programs (including @command{xdm} and
-@command{login}) in the N32 object format, whereas in older versions they
-were O32. For it to work, the @file{afskauthlib.so} library has to be in
-the same object format as the program that tries to load it. This might
-require that you have to configure and build for O32 in addition to the
-default N32.
-
-Apart from this it should ``just work''; there are no configuration
-files.
-
-Note that recent Irix 6.5 versions (at least 6.5.22) have PAM,
-including a @file{pam_krb5.so} module.  Not all relevant programs use
-PAM, though, e.g.@: @command{ssh}. In particular, for console
-graphical login you need to turn off @samp{visuallogin} and turn on
-@samp{xdm} with @command{chkconfig}.
-
-@node AFS, , Authentication modules, Applications
+@node AFS, , Applications, Applications
 @section AFS
 
 @cindex AFS
@@ -223,48 +69,3 @@ AFS-cell.
 If keyfile already exists, this will add the new key in afs-srvtab to
 KeyFile.
 
-@section Using 2b tokens with AFS
-
-@subsection What is 2b ?
-
-2b is the name of the proposal that was implemented to give basic
-Kerberos 5 support to AFS in rxkad. It's not real Kerberos 5 support
-since it still uses fcrypt for data encryption and not Kerberos
-encryption types.
-
-Its only possible (in all cases) to do this for DES encryption types
-because only then the token (the AFS equivalent of a ticket) will be
-smaller than the maximum size that can fit in the token cache in the
-OpenAFS/Transarc client. It is a so tight fit that some extra wrapping
-on the ASN1/DER encoding is removed from the Kerberos ticket.
-
-2b uses a Kerberos 5 EncTicketPart instead of a Kerberos 4 ditto for
-the part of the ticket that is encrypted with the service's key. The
-client doesn't know what's inside the encrypted data so to the client
-it doesn't matter.
-
-To  differentiate between Kerberos 4 tickets and Kerberos 5 tickets, 2b
-uses a special kvno, 213 for 2b tokens and 255 for Kerberos 5 tokens.
-
-Its a requirement that all AFS servers that support 2b also support
-native Kerberos 5 in rxkad.
-
-@subsection Configuring a Heimdal kdc to use 2b tokens
-
-Support for 2b tokens in the kdc are turned on for specific principals
-by adding them to the string list option @code{[kdc]use_2b} in the
-kdc's @file{krb5.conf} file.
-
-@example
-[kdc]
-       use_2b = @{
-               afs@@SU.SE = yes
-               afs/it.su.se@@SU.SE = yes
-       @}
-@end example
-
-@subsection Configuring AFS clients for 2b support
-
-There is no need to configure AFS clients for 2b support. The only
-software that needs to be installed/upgrade is a Kerberos 5 enabled
-@file{afslog}.
index d9f1a8c2e197b20ef9cfd52fc49bc5334d99956e..886bf2cdaa05bb0b3e01b231a0a9444e3a237eca 100644 (file)
@@ -10,9 +10,7 @@
 @end macro
 
 
-@node  Copyrights and Licenses, , Acknowledgments, Top
 @comment  node-name,  next,  previous,  up
-@appendix Copyrights and Licenses
 
 @heading Kungliga Tekniska Högskolan
 
index c8ef24969fb848224e26483343be4f16dfd19f1a..00041ca76c2d865c6e4f68481476ca853872119f 100644 (file)
@@ -1,4 +1,5 @@
 \input texinfo @c -*- texinfo -*-
+
 @c %**start of header
 @c $Id$
 @setfilename heimdal.info
@@ -7,9 +8,7 @@
 @afourpaper
 @end iftex
 @c some sensible characters, please?
-@tex
-\input latin1.tex
-@end tex
+@documentencoding UTF-8
 @setchapternewpage on
 @syncodeindex pg cp
 @c %**end of header
@@ -73,7 +72,6 @@ This manual for version @value{VERSION} of Heimdal.
 * Setting up a realm::          
 * Applications::                
 * Things in search for a better place::  
-* Kerberos 4 issues::           
 * Windows compatibility::  
 * Programming with Kerberos::   
 * Migration::                   
@@ -106,19 +104,8 @@ Setting up a realm
 
 Applications
 
-* Authentication modules::      
 * AFS::                         
 
-Authentication modules
-
-* Digital SIA::                 
-* IRIX::                        
-
-Kerberos 4 issues
-
-* Principal conversion issues::  
-* Converting a version 4 database::  
-
 Windows compatibility
 
 * Configuring Windows to use a Heimdal KDC::  
@@ -140,14 +127,14 @@ Programming with Kerberos
 @include setup.texi
 @include apps.texi
 @include misc.texi
-@include kerberos4.texi
 @include win2k.texi
 @include programming.texi
 @include migration.texi
 @include ack.texi
+@node  Copyrights and Licenses, , Acknowledgments, Top
+ @comment  node-name,  next,  previous,  up
 @include copyright.texi
 
 @c @shortcontents
-@contents
 
 @bye
index 0a90cb735282abcef69fea7cb5c17744cb43b931..4d0f05682aa5b7fe2c7c3b1824c4110b303f57f5 100644 (file)
@@ -7,9 +7,7 @@
 @afourpaper
 @end iftex
 @c some sensible characters, please?
-@tex
-\input latin1.tex
-@end tex
+@documentencoding UTF-8
 @setchapternewpage on
 @syncodeindex pg cp
 @c %**end of header
 @def@copyrightstart{}
 @def@copyrightend{}
 @end iftex
+@ifnottex
 @macro copynext
 @end macro
 @macro copyrightstart
 @end macro
 @macro copyrightend
 @end macro
+@end ifnottex
 
 @page
 @copyrightstart
diff --git a/third_party/heimdal/doc/kerberos4.texi b/third_party/heimdal/doc/kerberos4.texi
deleted file mode 100644 (file)
index 41a6508..0000000
+++ /dev/null
@@ -1,173 +0,0 @@
-@c $Id$
-
-@node Kerberos 4 issues, Windows compatibility, Things in search for a better place, Top
-@comment  node-name,  next,  previous,  up
-@chapter Kerberos 4 issues
-
-Kerberos 4 KDC and KA server have been moved.
-
-For more about AFS, see the section @xref{AFS}.
-
-@menu
-* Principal conversion issues::  
-* Converting a version 4 database::  
-@end menu
-
-@node Principal conversion issues, Converting a version 4 database, Kerberos 4 issues, Kerberos 4 issues
-@section Principal conversion issues
-
-First, Kerberos 4 and Kerberos 5 principals are different. A version 4
-principal consists of a name, an instance, and a realm. A version 5
-principal has one or more components, and a realm (the terms ``name''
-and ``instance'' are still used, for the first and second component,
-respectively).    Also, in some cases the name of a version 4 principal
-differs from the first component of the corresponding version 5
-principal. One notable example is the ``host'' type principals, where
-the version 4 name is @samp{rcmd} (for ``remote command''), and the
-version 5 name is @samp{host}. For the class of principals that has a
-hostname as instance, there is an other major difference, Kerberos 4
-uses only the first component of the hostname, whereas Kerberos 5 uses
-the fully qualified hostname.
-
-Because of this it can be hard or impossible to correctly convert a
-version 4 principal to a version 5 principal @footnote{the other way is
-not always trivial either, but usually easier}. The biggest problem is
-to know if the conversion resulted in a valid principal. To give an
-example, suppose you want to convert the principal @samp{rcmd.foo}.
-
-The @samp{rcmd} name suggests that the instance is a hostname (even if
-there are exceptions to this rule). To correctly convert the instance
-@samp{foo} to a hostname, you have to know which host it is referring
-to. You can to this by either guessing (from the realm) which domain
-name to append, or you have to have a list of possible hostnames. In the
-simplest cases you can cover most principals with the first rule. If you
-have several domains sharing a single realm this will not usually
-work. If the exceptions are few you can probably come by with a lookup
-table for the exceptions.
-
-In a complex scenario you will need some kind of host lookup mechanism.
-Using DNS for this is tempting, but DNS is error prone, slow and unsafe
-@footnote{at least until secure DNS is commonly available}.
-
-Fortunately, the KDC has a trump on hand: it can easily tell if a
-principal exists in the database. The KDC will use
-@code{krb5_425_conv_principal_ext} to convert principals when handling
-to version 4 requests.
-
-@node Converting a version 4 database, , Principal conversion issues, Kerberos 4 issues
-@section Converting a version 4 database
-
-If you want to convert an existing version 4 database, the principal
-conversion issue arises too.
-
-If you decide to convert your database once and for all, you will only
-have to do this conversion once. It is also possible to run a version 5
-KDC as a slave to a version 4 KDC. In this case this conversion will
-happen every time the database is propagated.  When doing this
-conversion, there are a few things to look out for. If you have stale
-entries in the database, these entries will not be converted. This might
-be because these principals are not used anymore, or it might be just
-because the principal couldn't be converted.
-
-You might also see problems with a many-to-one mapping of
-principals. For instance, if you are using DNS lookups and you have two
-principals @samp{rcmd.foo} and @samp{rcmd.bar}, where `foo' is a CNAME
-for `bar', the resulting principals will be the same. Since the
-conversion function can't tell which is correct, these conflicts will
-have to be resolved manually.
-
-@subsection Conversion example
-
-Given the following set of hosts and services:
-
-@example
-foo.se          rcmd
-mail.foo.se     rcmd, pop
-ftp.bar.se      rcmd, ftp
-@end example
-
-you have a database that consists of the following principals:
-
-@samp{rcmd.foo}, @samp{rcmd.mail}, @samp{pop.mail}, @samp{rcmd.ftp}, and
-@samp{ftp.ftp}.
-
-lets say you also got these extra principals: @samp{rcmd.gone},
-@samp{rcmd.old-mail}, where @samp{gone.foo.se} was a machine that has
-now passed away, and @samp{old-mail.foo.se} was an old mail machine that
-is now a CNAME for @samp{mail.foo.se}.
-
-When you convert this database you want the following conversions to be
-done:
-@example
-rcmd.foo         host/foo.se
-rcmd.mail        host/mail.foo.se
-pop.mail         pop/mail.foo.se
-rcmd.ftp         host/ftp.bar.se
-ftp.ftp          ftp/ftp.bar.se
-rcmd.gone        @i{removed}
-rcmd.old-mail    @i{removed}
-@end example
-
-A @file{krb5.conf} that does this looks like:
-
-@example
-[realms]
-        FOO.SE = @{
-                v4_name_convert = @{
-                        host = @{
-                                ftp = ftp
-                                pop = pop
-                                rcmd = host
-                        @}
-                @}
-                v4_instance_convert = @{
-                        foo = foo.se
-                        ftp = ftp.bar.se
-                @}
-                default_domain = foo.se
-        @}
-@end example
-
-The @samp{v4_name_convert} section says which names should be considered
-having an instance consisting of a hostname, and it also says how the
-names should be converted (for instance @samp{rcmd} should be converted
-to @samp{host}). The @samp{v4_instance_convert} section says how a
-hostname should be qualified (this is just a hosts-file in
-disguise). Host-instances that aren't covered by
-@samp{v4_instance_convert} are qualified by appending the contents of
-the @samp{default_domain}.
-
-Actually, this example doesn't work. Or rather, it works to well. Since
-it has no way of knowing which hostnames are valid and which are not, it
-will happily convert @samp{rcmd.gone} to @samp{host/gone.foo.se}. This
-isn't a big problem, but if you have run your kerberos realm for a few
-years, chances are big that you have quite a few `junk' principals.
-
-If you don't want this you can remove the @samp{default_domain}
-statement, but then you will have to add entries for @emph{all} your hosts
-in the @samp{v4_instance_convert} section.
-
-Instead of doing this you can use DNS to convert instances. This is not
-a solution without problems, but it is probably easier than adding lots
-of static host entries. 
-
-To enable DNS lookup you should turn on @samp{v4_instance_resolve} in
-the @samp{[libdefaults]} section.
-
-@subsection Converting a database
-
-The database conversion is done with @samp{hprop}. You can run this
-command to propagate the database to the machine called
-@samp{slave-server} (which should be running a @samp{hpropd}).
-
-@example
-hprop --source=krb4-db --master-key=/.m slave-server
-@end example
-
-This command can also be to use for converting the v4 database on the
-server:
-
-@example
-hprop -n --source=krb4-db -d /var/kerberos/principal --master-key=/.m | hpropd -n
-@end example
-
index 2fa7ede597a86303c04731555e3bdf0a41abd29b..7c3e1e70ef42aeebadab40527c05765d1c8f79a7 100644 (file)
@@ -16,6 +16,10 @@ To load the MIT Kerberos dump file, use the following command:
 kadmin can dump in MIT Kerberos format.  Simply run:
 @samp{kadmin -l dump -f MIT}.
 
+There are some limitations in this functionality.  Users should check
+the input dump and a native dump after loading to check for
+differences.
+
 The Heimdal KDC and kadmind, as well as kadmin -l and the libkadm5srv
 library can read and write MIT KDBs, and can read MIT stash files.  To
 build with KDB support requires having a standalone libdb from MIT
@@ -35,8 +39,6 @@ and hpropd.
 
 @section General issues
 
-When migrating from a Kerberos 4 KDC.
-
 @section Order in what to do things:
 
 @itemize @bullet
@@ -63,11 +65,5 @@ you can also check the kdc-log to check what ticket are checked out.
 
 @item Burn the bridge and change the master.
 @item Let all users use the Kerberos 5 tools by default.
-@item Turn off services that do not need Kerberos 4 authentication.
-
-Things that might be hard to get away is old programs with support for
-Kerberos 4. Example applications are old Eudora installations using
-KPOP, and Zephyr. Eudora can use the Kerberos 4 kerberos in the Heimdal
-kdc.
 
 @end itemize
index 1ad6aaab054ad1e3189dffcd341e253b65247589..2d976f45d76536e3006f015040cd95ee0f1dae54 100644 (file)
@@ -1,6 +1,6 @@
 @c $Id$
 
-@node Things in search for a better place, Kerberos 4 issues, Applications, Top
+@node Things in search for a better place, Windows compatibility, Applications, Top
 @chapter Things in search for a better place
 
 @section Making things work on Ciscos
index 0b3a860edb18cf1d19e6ce3aba658bd0120834b5..962541359eecec41e8f160a396f716d4b63da740 100644 (file)
@@ -6,15 +6,19 @@
 
 A
 @cindex realm
-realm is an administrative domain.  The name of a Kerberos realm is
-usually the Internet domain name in uppercase.  Call your realm the same
-as your Internet domain name if you do not have strong reasons for not
+realm is an administrative domain containing any number of Kerberos
+principals and namespaces.  The name of a Kerberos realm is
+usually a domain name in uppercase.  Call your realm the same
+as your site's domain name if you do not have strong reasons for not
 doing so.  It will make life easier for you and everyone else.
 
 @menu
 * Configuration file::
 * Creating the database::
 * Modifying the database::
+* Using namespaces and synthetic principals to keep the database small::
+* Using hard aliases for realm migration::
+* Using soft aliases for configuring referrals::
 * Checking the setup::
 * keytabs::
 * Remote administration::
@@ -40,7 +44,8 @@ To setup a realm you will first have to create a configuration file:
 @file{/etc/krb5.conf}. The @file{krb5.conf} file can contain many
 configuration options, some of which are described here.
 
-There is a sample @file{krb5.conf} supplied with the distribution.
+There is a sample @file{krb5.conf} supplied with the distribution, and
+a page for it in section 5 of the system manual.
 
 The configuration file is a hierarchical structure consisting of
 sections, each containing a list of bindings (either variable
@@ -84,11 +89,9 @@ are briefly described here.
 The @samp{libdefaults} section contains a list of library configuration
 parameters, such as the default realm and the timeout for KDC
 responses. The @samp{realms} section contains information about specific
-realms, such as where they hide their KDC@. This section serves the same
-purpose as the Kerberos 4 @file{krb.conf} file, but can contain more
-information. Finally the @samp{domain_realm} section contains a list of
-mappings from domains to realms, equivalent to the Kerberos 4
-@file{krb.realms} file.
+realms, such as where they hide their KDC@.
+Finally the @samp{domain_realm} section contains a list of
+mappings from domains to realms.
 
 To continue with the realm setup, you will have to create a configuration file,
 with contents similar to the following.
@@ -108,14 +111,17 @@ with contents similar to the following.
 
 @end example
 
-If you use a realm name equal to your domain name, you can omit the
-@samp{libdefaults}, and @samp{domain_realm}, sections. If you have a DNS
-SRV-record for your realm, or your Kerberos server has DNS CNAME
-@samp{kerberos.my.realm}, you can omit the @samp{realms} section too.
+When realm names correspond to domain names, one can avoid having to
+configure @samp{domain_realm} mappings, and one can avoid having to
+configure a @samp{default_realm} in the @samp{libdefaults} section.
+DNS SRV resource records can be used for KDC discovery, obviating the
+need list KDCs in the @samp{realms} section of the @samp{krb5.conf}
+file.
 
 @cindex KRB5_CONFIG
-If you want to use a different configuration file then the default you
-can point a file with the environment variable @samp{KRB5_CONFIG}.
+The Heimdal libraries and commands (and the MIT ones too), support the
+use of the environment variable @samp{KRB5_CONFIG} for using an
+alternative configuration.
 
 @example
 env KRB5_CONFIG=$HOME/etc/krb5.conf kinit user@@REALM
@@ -124,15 +130,16 @@ env KRB5_CONFIG=$HOME/etc/krb5.conf kinit user@@REALM
 @cindex GSS_MECH_CONFIG
 The GSS-API mechanism configuration file can also be changed from the
 default with the enviornment variable @samp{GSS_MECH_CONFIG}. Note that
-this file only configures additional plugin mechanisms: Kerberos, NTLM
-and SPNEGO are built in to the Heimdal GSS-API library.
+this file can only configure additional plugin mechanisms: Kerberos,
+NTLM and SPNEGO are built in to the Heimdal GSS-API library.
 
 @node Creating the database, Modifying the database, Configuration file, Setting up a realm
 @section Creating the database
 
-The database library will look for the database in the directory
-@file{@value{dbdir}}, so you should probably create that directory.
-Make sure the directory has restrictive permissions.
+The Heimdal database library, @code{libhdb}, will look for the
+database in the directory @file{@value{dbdir}}, so you should probably
+create that directory.  Make sure the directory has restrictive
+permissions.
 
 @example
 # mkdir /var/heimdal
@@ -141,8 +148,8 @@ Make sure the directory has restrictive permissions.
 
 Heimdal supports various database backends: lmdb (LMDB), db3 (Berkeley
 DB 3.x, 4.x, or 5.x), db1 (Berkeley DB 2.x), sqlite (SQLite3), and ldap
-(LDAP).  The default is @value{dbtype}, and is selected at build time
-from one of lmdb, db3, or db1.
+(LDAP).  The default is @value{dbtype}, and is selected at configure
+time from one of lmdb, db3, or db1.
 
 These defaults can be overriden in the 'database' key in the @samp{kdc}
 section of the configuration.
@@ -179,6 +186,11 @@ on which attackers can't do a dictionary attack.
 If you have a master key, make sure you make a backup of your master
 key file; without it backups of the database are of no use.
 
+Note that encryption of the keys in the database is only useful when
+the database is stored on external storage media that is easy to
+steal. Thus for the most part there is no need to encrypt the keys in
+the database.
+
 To initialise the database use the @command{kadmin} program, with the
 @kbd{-l} option (to enable local database mode). First issue a
 @kbd{init MY.REALM} command. This will create the database and insert
@@ -233,7 +245,7 @@ krbtgt/MY.REALM@@MY.REALM 1:0:1:52b53b61c875ce16:-:0:7:c8943be ...
 kadmin/changepw@@MY.REALM 1:0:1:f48c8af2b340e9fb:-:0:7:e3e6088 ...
 @end smallexample
 
-@node Modifying the database, Checking the setup, Creating the database, Setting up a realm
+@node Modifying the database, Using namespaces and synthetic principals to keep the database small, Creating the database, Setting up a realm
 @section Modifying the database
 
 All modifications of principals are done with with kadmin.
@@ -295,7 +307,101 @@ R second
 
 @c Describe more of kadmin commands here...
 
-@node Checking the setup, keytabs, Modifying the database, Setting up a realm
+@node Using namespaces and synthetic principals to keep the database small, Checking the setup, Modifying the database, Setting up a realm
+@section Using namespaces and synthetic principals to keep the database small
+
+Keeping a Kerberos database small is useful for several reasons:
+
+@itemize @bullet
+@item to avoid low write transaction rates
+@item to avoid replication latency
+@item to keep re-keying costs down
+@end itemize
+
+To avoid needing database entries for client principals, configure and
+enable PKINIT and synthetic principals. Alternatively, configure and
+enable the use of GSS-API pre-authentication, though this is currently
+experimental.
+
+With synthetic client principals enabled, client principals will be
+deemed to exist if they can pre-authenticate using a method that
+yields an authenticated principal name, and if the client principal
+does not already exist.
+
+To lock out or disable a specific synthetic client principal, create
+it in the database with the desired attributes.
+
+To avoid needing database entries for host-based service principals,
+create virtual host-based service principal namespaces using the
+@command{add_ns} sub-command of the @command{kadmin} command. Virtual
+host-based service principals will exist for every possible hostname
+under a containing namespace, with keys derived from the namespace's
+based keys and the current key rotation period. The long-term keys of
+virtual host-based service principals rotate on a hard schedule as
+configured for their namespaces, so hosts and applications using them
+must keep re-fetching their @samp{keytabs}. See the manual pages for
+@file{krb5.conf}, @command{kadmin}, and @command{httpkadmind} for more
+details.
+
+Using these features one can end up with a database that contains just
+@code{krbtgt} principals, principals for locked users, and principals
+that are neither @code{krbtgt}, user, nor host-based services.
+
+@node Using hard aliases for realm migration, Using soft aliases for configuring referrals, Using namespaces and synthetic principals to keep the database small, Setting up a realm
+@section Using hard aliases for realm migration
+
+The Heimdal @command{kadmin} command can be used to add aliases to
+principal entries in the Heimdal database.  Aliases of principals of
+the form @samp{WELLKNOWN/REFERRALS/TARGET} or
+@samp{WELLKNOWN/REFERRALS/TARGET/anything} are "soft" aliases.
+Aliases of principals of other forms are "hard" aliases.
+
+When a client makes a request for a principal's alias, and it does not
+use the KDC request "canonicalize" option flag, the Heimdal KDC will
+treat the alias as a distinct principal that happens to share
+attributes and long-term symmetric keys and salts with the principal
+it is an alias of.
+
+This is useful for, for example, ensuring that host-based principals
+can be referred to by any aliases.
+
+This can also be very useful for renaming realms: add new
+@code{krbtgt} principals for the new realms, then add aliases to
+existing principals in their new realms. For example, a user with a
+principal @code{joe@@A} can be given an alias of
+@code{joes@@B}, and
+then they can @code{kinit joes@@B} and get Kerberos tickets for
+@code{joes@@B}. Similarly, a service principal such as
+@code{HTTP/foo.bar.baz.example@@BAZ.EXAMPLE} can be given an alias such as
+@code{HTTP/foo.bar.baz.example@@BAR.BAZ.EXAMPLE}, or even
+@code{HTTP/foobar.new-domain.example@@NEW-DOMAIN.EXAMPLE}, and
+requesting tickets with those aliases as the service names will work.
+
+@node Using soft aliases for configuring referrals, Checking the setup, Using hard aliases for realm migration, Setting up a realm
+@section Using soft aliases for configuring referrals
+
+Soft aliases, which are aliases of principals of the form
+@code{WELLKNOWN/REFERRALS/TARGET} or
+@code{WELLKNOWN/REFERRALS/TARGET/anything}, are used to generate
+referrals to other realms. Specifically, the realm of a soft alias'
+canonical name is the realm to issue referrals to.
+
+Soft aliases can be used to configure individual referrals, but also
+of entire namespaces of hostnames. To configure the issuance of
+referrals for entire namespaces, make a soft alias of the form
+@code{WELLKNOWN/HOSTBASED-NAMESPACE/service/namespace-fqdn@@REALM} to
+have the TGS for that @samp{REALM} issue referrals for all principals
+of the form @code{service/hostname@@REALM} where the hostname component
+is a sub-domain of the namespace component of the alias name.
+
+For example, a soft alias name
+@code{WELLKNOWN/HOSTBASED-NAMESPACE/host/cloud.bar.example@@BAR.EXAMPLE}
+to a realm @samp{B} will cause the KDC to issue referrals to @samp{B}
+for any principals such as
+@samp{host/foo.cloud.bar.example@@BAR.EXAMPLE}, and
+@samp{host/baz.cloud.bar.example@@BAR.EXAMPLE}, and so on.
+
+@node Checking the setup, keytabs, Using namespaces and synthetic principals to keep the database small, Setting up a realm
 @section Checking the setup
 
 There are two tools that can check the consistency of the Kerberos
@@ -674,10 +780,6 @@ fixed size encryption key.
 In Kerberos 5 the salt is determined by the encryption type, except in
 some special cases.
 
-In @code{des} there is the Kerberos 4 salt
-(none at all) or the afs-salt (using the cell (realm in
-AFS lingo)).
-
 In @code{arcfour} (the encryption type that Microsoft Windows 2000 uses)
 there is no salt. This is to be compatible with NTLM keys in Windows
 NT 4.
@@ -696,12 +798,6 @@ no salt at all).
 Common types of salting include
 
 @itemize @bullet
-@item @code{v4} (or @code{des:pw-salt:})
-
-The Kerberos 4 salting is using no salt at all. Reason there is colon
-at the end of the salt string is that it makes the salt the empty
-string (same as no salt).
-
 @item @code{v5} (or @code{pw-salt})
 
 @code{pw-salt} uses the default salt for each encryption type is
index 902344b03529df9a8463ed442362c28b31b51ccc..2b0e98de55214e460f73fd6d124f44846695f4f9 100644 (file)
@@ -133,8 +133,10 @@ It would be possible to add a @dfn{replay cache}
 to the server side.  The idea is to save the authenticators sent during
 the last few minutes, so that @var{B} can detect when someone is trying
 to retransmit an already used message. This is somewhat impractical
-(mostly regarding efficiency), and is not part of Kerberos 4; MIT
-Kerberos 5 contains it.
+(mostly regarding performance); MIT Kerberos 5 has a replay cache,
+while Heimdal does not.
+
+However, most GSS-API applicatons do not need a replay cache at all.
 
 To authenticate @var{B}, @var{A} might request that @var{B} sends
 something back that proves that @var{B} has access to the session
index 0fefeee3fdc609c19dded796ef7f4e2907bd2468..d4ab2fecda9392741d9d614b2365bd790d0bf8fc 100644 (file)
@@ -1,7 +1,7 @@
 @c $Id$
 
 
-@node Windows compatibility, Programming with Kerberos, Kerberos 4 issues, Top
+@node Windows compatibility, Programming with Kerberos, Things in search for a better place, Top
 @comment  node-name,  next,  previous,  up
 @chapter Windows compatibility
 
@@ -126,8 +126,7 @@ You also need to add the inter-realm keys to the Heimdal KDC. But take
 care to the encryption types and salting used for those keys. There should be
 no encryption type stronger than the one configured on Windows side for this
 relationship, itself limited to the ones supported by this specific version of
-Windows, nor any Kerberos 4 salted hashes, as Windows does not seem to
-understand them. Otherwise, the trust will not works.
+Windows. Otherwise, the trust will not works.
 
 Here are the version-specific needed information:
 @enumerate
@@ -235,11 +234,11 @@ analysing the data.
 @comment  node-name,  next,  previous,  up
 @section Quirks of Windows 2000 KDC
 
-There are some issues with salts and Windows 2000.  Using an empty salt---which is the only one that Kerberos 4 supported, and is therefore known
-as a Kerberos 4 compatible salt---does not work, as far as we can tell
-from out experiments and users' reports.  Therefore, you have to make
-sure you keep around keys with all the different types of salts that are
-required.  Microsoft have fixed this issue post Windows 2003.
+There are some issues with salts and Windows 2000.  Using an empty salt does
+not work, as far as we can tell from out experiments and users' reports.
+Therefore, you have to make sure you keep around keys with all the different
+types of salts that are required.  Microsoft have fixed this issue post Windows
+2003.
 
 Microsoft seems also to have forgotten to implement the checksum
 algorithms @samp{rsa-md4-des} and @samp{rsa-md5-des}. This can make Name
index 5521181d27ccaac8e009da23981c53162e9f22d5..6e0f6bcf14705c62b8f53af210f572a1ab38cceb 100644 (file)
  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
  * OF THE POSSIBILITY OF SUCH DAMAGE.
- * 
+ *
  **********************************************************************/
 
 #ifndef __CONFIG_H__
 #define __CONFIG_H__
 
-#define fallthrough do {} while(0) /* fallthrough */
+#define HEIM_FALLTHROUGH do {} while(0) /* fallthrough */
 
 #ifndef RCSID
 #define RCSID(msg) \
index 80d05ec7adbfddcf722c7ec9d1779cd380c437c8..c7f2b7f6b5c7a0efa9d515200f7e5eedbcc94924 100644 (file)
@@ -119,17 +119,17 @@ $(OBJ)\add_random_users.exe: $(OBJ)\add_random_users.obj $(LIBKADM5SRV) $(LIBKAD
        $(EXECONLINK) Secur32.lib Shell32.lib
        $(EXEPREP_NODIST)
 
-TEST_BINARIES=$(OBJ)\test_util.exe
-
-$(OBJ)\test_util.exe: $(OBJ)\test_util.obj $(OBJ)\util.obj $(KADMIN_LIBS)
-       $(EXECONLINK) Secur32.lib Shell32.lib
-       $(EXEPREP_NODIST)
-
-test-binaries: $(TEST_BINARIES)
-
-test-run:
-       cd $(OBJ)
-       test_util.exe
-       cd $(SRCDIR)
-
-test:: test-binaries test-run
+#TEST_BINARIES=$(OBJ)\test_util.exe
+#
+#$(OBJ)\test_util.exe: $(OBJ)\test_util.obj $(OBJ)\util.obj $(KADMIN_LIBS)
+#      $(EXECONLINK) Secur32.lib Shell32.lib
+#      $(EXEPREP_NODIST)
+#
+#test-binaries: $(TEST_BINARIES)
+#
+#test-run:
+#      cd $(OBJ)
+#      test_util.exe
+#      cd $(SRCDIR)
+#
+test:: #test-binaries test-run
index ad14ed224c4c665fd6b8dd426c8ea97aab68f3eb..a8782ca43bfd1723f3e004f91bf46592a4fc9eff 100644 (file)
@@ -73,7 +73,7 @@ do_check_entry(krb5_principal principal, void *data)
        return 1;
 
     memset (&princ, 0, sizeof(princ));
-    ret = kadm5_get_principal(kadm_handle, principal, &princ,
+    ret = kadm5_get_principal(data, principal, &princ,
                              KADM5_PRINCIPAL | KADM5_KEY_DATA);
     if(ret) {
        krb5_warn(context, ret, "Failed to get principal: %s", name);
@@ -95,7 +95,7 @@ do_check_entry(krb5_principal principal, void *data)
     }
 
     free(name);
-    kadm5_free_principal_ent(kadm_handle, &princ);
+    kadm5_free_principal_ent(data, &princ);
 
     return 0;
 }
@@ -106,6 +106,7 @@ check(void *opt, int argc, char **argv)
     kadm5_principal_ent_rec ent;
     krb5_error_code ret;
     char *realm = NULL, *p, *p2;
+    void *inner_kadm_handle = NULL;
     int found;
 
     if (argc == 0) {
@@ -254,7 +255,15 @@ check(void *opt, int argc, char **argv)
        }
     }
 
-    foreach_principal("*", do_check_entry, "check", NULL);
+    ret = kadm5_dup_context(kadm_handle, &inner_kadm_handle);
+    if (ret == 0)
+        ret = foreach_principal("*", do_check_entry, "check", inner_kadm_handle);
+    if (inner_kadm_handle)
+        kadm5_destroy(inner_kadm_handle);
+    if (ret) {
+        krb5_warn(context, ret, "Could not iterate principals in realm");
+        goto fail;
+    }
 
     free(realm);
     return 0;
index 2f3c1c1bcd74f921d05fe8c52fce6f47a27afafe..7ffc828cf307ef207c2eb79127fde28dc778e627 100644 (file)
@@ -40,18 +40,19 @@ struct cpw_entry_data {
     int random_password;
     char *password;
     krb5_key_data *key_data;
+    void *kadm_handle;
 };
 
 static int
-set_random_key (krb5_principal principal, int keepold)
+set_random_key(void *dup_kadm_handle, krb5_principal principal, int keepold)
 {
     krb5_error_code ret;
     int i;
     krb5_keyblock *keys;
     int num_keys;
 
-    ret = kadm5_randkey_principal_3(kadm_handle, principal, keepold, 0, NULL,
-                                   &keys, &num_keys);
+    ret = kadm5_randkey_principal_3(dup_kadm_handle, principal, keepold, 0,
+                                    NULL, &keys, &num_keys);
     if(ret)
        return ret;
     for(i = 0; i < num_keys; i++)
@@ -61,7 +62,9 @@ set_random_key (krb5_principal principal, int keepold)
 }
 
 static int
-set_random_password (krb5_principal principal, int keepold)
+set_random_password(void *dup_kadm_handle,
+                    krb5_principal principal,
+                    int keepold)
 {
     krb5_error_code ret;
     char pw[128];
@@ -72,7 +75,8 @@ set_random_password (krb5_principal principal, int keepold)
        return ret;
 
     random_password(pw, sizeof(pw));
-    ret = kadm5_chpass_principal_3(kadm_handle, principal, keepold, 0, NULL, pw);
+    ret = kadm5_chpass_principal_3(dup_kadm_handle, principal, keepold, 0,
+                                   NULL, pw);
     if (ret == 0)
        printf ("%s's password set to \"%s\"\n", princ_name, pw);
     free(princ_name);
@@ -81,7 +85,10 @@ set_random_password (krb5_principal principal, int keepold)
 }
 
 static int
-set_password (krb5_principal principal, char *password, int keepold)
+set_password(void *dup_kadm_handle,
+             krb5_principal principal,
+             char *password,
+             int keepold)
 {
     krb5_error_code ret = 0;
     char pwbuf[128];
@@ -108,18 +115,21 @@ set_password (krb5_principal principal, char *password, int keepold)
        password = pwbuf;
     }
     if(ret == 0)
-       ret = kadm5_chpass_principal_3(kadm_handle, principal, keepold, 0, NULL,
-                                      password);
+        ret = kadm5_chpass_principal_3(dup_kadm_handle, principal, keepold, 0,
+                                       NULL, password);
     memset_s(pwbuf, sizeof(pwbuf), 0, sizeof(pwbuf));
     return ret;
 }
 
 static int
-set_key_data (krb5_principal principal, krb5_key_data *key_data, int keepold)
+set_key_data(void *dup_kadm_handle,
+             krb5_principal principal,
+             krb5_key_data *key_data,
+             int keepold)
 {
     krb5_error_code ret;
 
-    ret = kadm5_chpass_principal_with_key_3(kadm_handle, principal, keepold,
+    ret = kadm5_chpass_principal_with_key_3(dup_kadm_handle, principal, keepold,
                                            3, key_data);
     return ret;
 }
@@ -130,13 +140,13 @@ do_cpw_entry(krb5_principal principal, void *data)
     struct cpw_entry_data *e = data;
 
     if (e->random_key)
-       return set_random_key (principal, e->keepold);
+       return set_random_key(e->kadm_handle, principal, e->keepold);
     else if (e->random_password)
-       return set_random_password (principal, e->keepold);
+       return set_random_password(e->kadm_handle, principal, e->keepold);
     else if (e->key_data)
-       return set_key_data (principal, e->key_data, e->keepold);
+       return set_key_data(e->kadm_handle, principal, e->key_data, e->keepold);
     else
-       return set_password (principal, e->password, e->keepold);
+       return set_password(e->kadm_handle, principal, e->password, e->keepold);
 }
 
 int
@@ -148,6 +158,10 @@ cpw_entry(struct passwd_options *opt, int argc, char **argv)
     int num;
     krb5_key_data key_data[3];
 
+    data.kadm_handle = NULL;
+    ret = kadm5_dup_context(kadm_handle, &data.kadm_handle);
+    if (ret)
+        krb5_err(context, 1, ret, "Could not duplicate kadmin connection");
     data.random_key = opt->random_key_flag;
     data.random_password = opt->random_password_flag;
     data.password = opt->password_string;
@@ -206,6 +220,8 @@ cpw_entry(struct passwd_options *opt, int argc, char **argv)
     for(i = 0; i < argc; i++)
        ret = foreach_principal(argv[i], do_cpw_entry, "cpw", &data);
 
+    kadm5_destroy(data.kadm_handle);
+
     if (data.key_data) {
        int16_t dummy;
        kadm5_free_key_data (kadm_handle, &dummy, key_data);
index a066f56ea3875a240a6f2f07a1e9fd5370a0c621..320fe6e8eabcac03e35d626717758fa931c35a05 100644 (file)
@@ -37,7 +37,7 @@
 static int
 do_del_entry(krb5_principal principal, void *data)
 {
-    return kadm5_delete_principal(kadm_handle, principal);
+    return kadm5_delete_principal(data, principal);
 }
 
 int
@@ -45,12 +45,15 @@ del_entry(void *opt, int argc, char **argv)
 {
     int i;
     krb5_error_code ret = 0;
+    void *dup_kadm_handle = NULL;
 
-    for(i = 0; i < argc; i++) {
-       ret = foreach_principal(argv[i], do_del_entry, "del", NULL);
-       if (ret)
-           break;
-    }
+    ret = kadm5_dup_context(kadm_handle, &dup_kadm_handle);
+
+    for (i = 0; ret == 0 && i < argc; i++)
+       ret = foreach_principal(argv[i], do_del_entry, "del", dup_kadm_handle);
+
+    if (dup_kadm_handle)
+        kadm5_destroy(dup_kadm_handle);
     return ret != 0;
 }
 
@@ -91,12 +94,14 @@ del_namespace(void *opt, int argc, char **argv)
 {
     int i;
     krb5_error_code ret = 0;
-
-    for(i = 0; i < argc; i++) {
-       ret = foreach_principal(argv[i], do_del_ns_entry, "del_ns", NULL);
-       if (ret)
-           break;
-    }
+    void *dup_kadm_handle = NULL;
+
+    ret = kadm5_dup_context(kadm_handle, &dup_kadm_handle);
+    for (i = 0; ret == 0 && i < argc; i++)
+        ret = foreach_principal(argv[i], do_del_ns_entry, "del_ns",
+                                dup_kadm_handle);
+    if (dup_kadm_handle)
+        kadm5_destroy(dup_kadm_handle);
     return ret != 0;
 }
 
index adb2e28518aad8f0097e4d14043e5a9db4fe8bd6..04d4d79a17ba1aa167019279e9b1723df2971c3e 100644 (file)
@@ -40,6 +40,7 @@ struct ext_keytab_data {
     int random_key_flag;
     size_t nkstuple;
     krb5_key_salt_tuple *kstuple;
+    void *kadm_handle;
 };
 
 static int
@@ -59,7 +60,7 @@ do_ext_keytab(krb5_principal principal, void *data)
     if (!e->random_key_flag)
         mask |= KADM5_KVNO | KADM5_KEY_DATA;
 
-    ret = kadm5_get_principal(kadm_handle, principal, &princ, mask);
+    ret = kadm5_get_principal(e->kadm_handle, principal, &princ, mask);
     if (ret)
        return ret;
 
@@ -112,7 +113,7 @@ do_ext_keytab(krb5_principal principal, void *data)
             n_k++;
        }
     } else if (e->random_key_flag) {
-        ret = kadm5_randkey_principal_3(kadm_handle, principal, e->keep,
+        ret = kadm5_randkey_principal_3(e->kadm_handle, principal, e->keep,
                                         e->nkstuple, e->kstuple, &k, &n_k);
        if (ret)
            goto out;
@@ -140,7 +141,7 @@ do_ext_keytab(krb5_principal principal, void *data)
     }
 
   out:
-    kadm5_free_principal_ent(kadm_handle, &princ);
+    kadm5_free_principal_ent(e->kadm_handle, &princ);
     if (k) {
         for (i = 0; i < n_k; i++)
             memset(k[i].keyvalue.data, 0, k[i].keyvalue.length);
@@ -159,6 +160,10 @@ ext_keytab(struct ext_keytab_options *opt, int argc, char **argv)
     const char *enctypes;
     size_t i;
 
+    data.kadm_handle = NULL;
+    ret = kadm5_dup_context(kadm_handle, &data.kadm_handle);
+    if (ret)
+        krb5_err(context, 1, ret, "Could not duplicate kadmin connection");
     data.random_key_flag = opt->random_key_flag;
     data.keep = 1;
     i = 0;
@@ -209,6 +214,7 @@ ext_keytab(struct ext_keytab_options *opt, int argc, char **argv)
            break;
     }
 
+    kadm5_destroy(data.kadm_handle);
     krb5_kt_close(context, data.keytab);
     free(data.kstuple);
     return ret != 0;
index a884e11e96be481fb40cdc39708871d5c24702f9..6e8ada01ea48e0cae59788864cf3ad7160ffe768 100644 (file)
@@ -83,7 +83,9 @@ struct get_entry_data {
     uint32_t extra_mask;
     struct field_info *chead, **ctail;
     const char *krb5_config_fname;
+    void *kadm_handle;
     uint32_t n;
+    int upto;
 };
 
 static int
@@ -478,13 +480,18 @@ do_get_entry(krb5_principal principal, void *data)
     krb5_error_code ret;
     struct get_entry_data *e = data;
 
+    if (e->upto == 0)
+        return EINTR;
+    if (e->upto > 0)
+        e->upto--;
+
     memset(&princ, 0, sizeof(princ));
-    ret = kadm5_get_principal(kadm_handle, principal,
+    ret = kadm5_get_principal(e->kadm_handle, principal,
                              &princ,
                              e->mask | e->extra_mask);
     if (ret == 0) {
         (e->format)(e, &princ);
-        kadm5_free_principal_ent(kadm_handle, &princ);
+        kadm5_free_principal_ent(e->kadm_handle, &princ);
     }
 
     e->n++;
@@ -534,8 +541,14 @@ static int
 do_list_entry(krb5_principal principal, void *data)
 {
     char buf[1024];
+    int *upto = data;
     krb5_error_code ret;
 
+    if (*upto == 0)
+        return EINTR;
+    if (*upto > 0)
+        (*upto)--;
+
     ret = krb5_unparse_name_fixed_short(context, principal, buf, sizeof(buf));
     if (ret != 0)
         return ret;
@@ -544,13 +557,13 @@ do_list_entry(krb5_principal principal, void *data)
 }
 
 static int
-listit(const char *funcname, int argc, char **argv)
+listit(const char *funcname, int upto, int argc, char **argv)
 {
     int i;
     krb5_error_code ret, saved_ret = 0;
 
     for (i = 0; i < argc; i++) {
-       ret = foreach_principal(argv[i], do_list_entry, funcname, NULL);
+       ret = foreach_principal(argv[i], do_list_entry, funcname, &upto);
         if (saved_ret == 0 && ret != 0)
             saved_ret = ret;
     }
@@ -577,14 +590,19 @@ getit(struct get_options *opt, const char *name, int argc, char **argv)
        opt->short_flag = 1;
 
     if (opt->terse_flag)
-        return listit(name, argc, argv);
+        return listit(name, opt->upto_integer, argc, argv);
 
+    data.kadm_handle = NULL;
+    ret = kadm5_dup_context(kadm_handle, &data.kadm_handle);
+    if (ret)
+        krb5_err(context, 1, ret, "Could not duplicate kadmin connection");
     data.table = NULL;
     data.chead = NULL;
     data.ctail = &data.chead;
     data.mask = 0;
     data.extra_mask = 0;
     data.krb5_config_fname = opt->krb5_config_file_string;
+    data.upto = opt->upto_integer;
     data.n = 0;
 
     if(opt->short_flag) {
@@ -610,6 +628,8 @@ getit(struct get_options *opt, const char *name, int argc, char **argv)
     for(i = 0; i < argc; i++)
        ret = foreach_principal(argv[i], do_get_entry, name, &data);
 
+    kadm5_destroy(data.kadm_handle);
+
     if(data.table != NULL) {
        rtbl_format(data.table, stdout);
        rtbl_destroy(data.table);
@@ -638,5 +658,6 @@ list_princs(struct list_options *opt, int argc, char **argv)
     get_opt.short_flag = opt->short_flag;
     get_opt.terse_flag = opt->terse_flag;
     get_opt.column_info_string = opt->column_info_string;
+    get_opt.upto_integer = opt->upto_integer;
     return getit(&get_opt, "list", argc, argv);
 }
index e8a1e8a08f21afaf6044d1d9ae3c7cbbb15d3b98..db9c4415e6c5e813d6fc7599ef40fa507a89f188 100644 (file)
@@ -500,6 +500,12 @@ command = {
                type = "string"
                help = "filename to save the principal's krb5.confg in"
        }
+       option = {
+               long = "upto"
+               type = "integer"
+                default = "-1"
+               help = "maximum number of principals to get/list"
+       }
        argument = "principal..."
        min_args = "1"
        help = "Shows information about principals matching the expressions."
@@ -674,6 +680,13 @@ command = {
        option = {
                long = "krb5-config-file"
                type = "string"
+                help = "only use this option with the get command"
+       }
+       option = {
+               long = "upto"
+               type = "integer"
+                default = "-1"
+               help = "maximum number of principals to get/list"
        }
        argument = "principal..."
        min_args = "1"
index b0e852931c6b33f4c50a93bd2282fa4279bbd104..ded599794616bde9c579514fad269a18a482426b 100644 (file)
@@ -150,14 +150,34 @@ This command has the following aliases:
 .Bd -ragged -offset indent
 Adds one or more aliases to the given principal.
 .Pp
-When a client requests a service ticket for a service principal
-name that is an alias of a principal in a different realm, the
-TGS will return a referral to that realm.
-This compares favorably to using
+There are two types of aliases: hard, and soft.
+A soft alias is an alias of a principal of the form
+.Ar WELLKNOWN/REFERRALS/TARGET@target_realm
+or
+.Ar WELLKNOWN/REFERRALS/TARGET/arbitrary-component@target_realm .
+A hard alias is an alias of any normal principal, even if in a
+different realm.
+.Pp
+Hard aliases are treated as distinct principals sharing
+attributes and keys with their canonical principals.
+If a client requests canonicalization of a hard alias name, the
+KDC will use the canonical name in the ticket issued as long as
+the alias and canonical names are in the same realm.
+Conversely, if a client does not request canonicalization, or if
+the hard alias and the canonical name have different realms, then
+the KDC will issue a ticket for the alias name.
+.Pp
+Soft aliases can only be used to configure the production of
+referrals by the KDC.
+When a client requests a ticket for a principal that turns out to
+be a soft alias, the KDC will respond with a referral to the
+alias' canonical name's realm.
+.Pp
+Soft aliasing compares favorably to using
 .Ar [domain_realm]
 entries in the KDC's
-.Ar krb5.conf ,
-but may be managed via the
+.Ar krb5.conf :
+soft aliases may be managed via the
 .Nm kadmin
 command and its
 .Nm add_alias
@@ -166,9 +186,9 @@ and
 sub-commands rather than having to edit the KDC's configuration
 file and having to restart the KDC.
 .Pp
-There are two methods for issuing referrals for entire namespaces
-of hostnames.
-An alias of the form
+There are two methods for configuring the issuance of referrals
+for entire namespaces of hostnames.
+A soft alias of the form
 .Ar  WELLKNOWN/HOSTBASED-NAMESPACE/service/namespace-fqdn@REALM
 (see
 .Nm add_namespace
@@ -402,11 +422,15 @@ only change the ones specified.
 .Pp
 The
 .Fl Fl alias= Ns Ar alias-name
-option may be given multiple times, which will set the complete
-list of aliases for the principal.
+option may be given multiple times.
+If this option is used at all, the complete list of aliases must
+be given, with one option per-alias.
+If the list given has fewer aliases than the principal had prior
+to the modification, then the missing aliases will be deleted.
+.Pp
 Use the
 .Nm add_alias
-command instead to add an alias without having to list all
+command instead to add an alias to avoid having to list all
 existing aliases to keep.
 .Pp
 The
index d76265b6cf7ed84ab6b39d6cf97ce776c23ead1d..6ad36b9094c4ad1331dbf78c3c517558e15a975a 100644 (file)
@@ -98,6 +98,7 @@
 
 extern krb5_context context;
 extern void * kadm_handle;
+extern int list_chunk_size;
 
 #undef ALLOC
 #define ALLOC(X) ((X) = malloc(sizeof(*(X))))
index 444950623f0b7c0c1f5e8ded0b822fccad33cb86..cf335d6dc01eceb25acbc0328af0c5c0dae8626f 100644 (file)
@@ -45,6 +45,7 @@ static int debug_flag;
 static int readonly_flag;
 static char *port_str;
 char *realm;
+int list_chunk_size = -1;
 
 static int detach_from_console = -1;
 int daemon_child = -1;
@@ -72,6 +73,9 @@ static struct getargs args[] = {
     {  "debug",        'd',    arg_flag,   &debug_flag,
        "enable debugging", NULL
     },
+    {   "list-chunk-size", 0,   arg_integer,&list_chunk_size,
+        "set the LIST streaming count of names per chunk", "NUMBER"
+    },
     {
         "detach",       0 ,      arg_flag, &detach_from_console,
         "detach from console", NULL
index 7c7b2dd7ce447bcaadc739d95777aad402b2785d..3bcd9ac31d58642d38a1658a770d163fc6f6a555 100644 (file)
@@ -308,16 +308,24 @@ add_krb5_config(kadm5_principal_ent_rec *princ, const char *fname)
     add_tl(princ, KRB5_TL_EXTENSION, &buf);
 }
 
+struct mod_data {
+    struct modify_namespace_key_rotation_options *opt_ns_kr;
+    struct modify_namespace_options *opt_ns;
+    struct modify_options *opt;
+    void *kadm_handle;
+};
+
 static int
 do_mod_entry(krb5_principal principal, void *data)
 {
     krb5_error_code ret;
     kadm5_principal_ent_rec princ;
     int mask = 0;
-    struct modify_options *e = data;
+    struct mod_data *m = data;
+    struct modify_options *e = m->opt;
 
     memset (&princ, 0, sizeof(princ));
-    ret = kadm5_get_principal(kadm_handle, principal, &princ,
+    ret = kadm5_get_principal(m->kadm_handle, principal, &princ,
                              KADM5_PRINCIPAL | KADM5_ATTRIBUTES |
                              KADM5_MAX_LIFE | KADM5_MAX_RLIFE |
                              KADM5_PRINC_EXPIRE_TIME |
@@ -382,12 +390,12 @@ do_mod_entry(krb5_principal principal, void *data)
     } else
        ret = edit_entry(&princ, &mask, NULL, 0);
     if(ret == 0) {
-       ret = kadm5_modify_principal(kadm_handle, &princ, mask);
+       ret = kadm5_modify_principal(m->kadm_handle, &princ, mask);
        if(ret)
            krb5_warn(context, ret, "kadm5_modify_principal");
     }
 
-    kadm5_free_principal_ent(kadm_handle, &princ);
+    kadm5_free_principal_ent(m->kadm_handle, &princ);
     return ret;
 }
 
@@ -395,13 +403,19 @@ int
 mod_entry(struct modify_options *opt, int argc, char **argv)
 {
     krb5_error_code ret = 0;
+    struct mod_data data;
     int i;
 
-    for(i = 0; i < argc; i++) {
-       ret = foreach_principal(argv[i], do_mod_entry, "mod", opt);
-       if (ret)
-           break;
-    }
+    data.kadm_handle = NULL;
+    data.opt_ns_kr = NULL;
+    data.opt_ns = NULL;
+    data.opt = opt;
+
+    ret = kadm5_dup_context(kadm_handle, &data.kadm_handle);
+    for (i = 0; ret == 0 && i < argc; i++)
+       ret = foreach_principal(argv[i], do_mod_entry, "mod", &data);
+    if (data.kadm_handle)
+        kadm5_destroy(data.kadm_handle);
     return ret != 0;
 }
 
@@ -411,10 +425,11 @@ do_mod_ns_entry(krb5_principal principal, void *data)
     krb5_error_code ret;
     kadm5_principal_ent_rec princ;
     int mask = 0;
-    struct modify_namespace_options *e = data;
+    struct mod_data *m = data;
+    struct modify_namespace_options *e = m->opt_ns;
 
     memset (&princ, 0, sizeof(princ));
-    ret = kadm5_get_principal(kadm_handle, principal, &princ,
+    ret = kadm5_get_principal(m->kadm_handle, principal, &princ,
                              KADM5_PRINCIPAL | KADM5_ATTRIBUTES |
                              KADM5_MAX_LIFE | KADM5_MAX_RLIFE |
                              KADM5_PRINC_EXPIRE_TIME |
@@ -441,12 +456,12 @@ do_mod_ns_entry(krb5_principal principal, void *data)
     } else
        ret = edit_entry(&princ, &mask, NULL, 0);
     if(ret == 0) {
-       ret = kadm5_modify_principal(kadm_handle, &princ, mask);
+       ret = kadm5_modify_principal(m->kadm_handle, &princ, mask);
        if(ret)
            krb5_warn(context, ret, "kadm5_modify_principal");
     }
 
-    kadm5_free_principal_ent(kadm_handle, &princ);
+    kadm5_free_principal_ent(m->kadm_handle, &princ);
     return ret;
 }
 
@@ -454,13 +469,19 @@ int
 modify_namespace(struct modify_namespace_options *opt, int argc, char **argv)
 {
     krb5_error_code ret = 0;
+    struct mod_data data;
     int i;
 
-    for(i = 0; i < argc; i++) {
-       ret = foreach_principal(argv[i], do_mod_ns_entry, "mod_ns", opt);
-       if (ret)
-           break;
-    }
+    data.kadm_handle = NULL;
+    data.opt_ns_kr = NULL;
+    data.opt_ns = opt;
+    data.opt = NULL;
+
+    ret = kadm5_dup_context(kadm_handle, &data.kadm_handle);
+    for (i = 0; ret == 0 && i < argc; i++)
+       ret = foreach_principal(argv[i], do_mod_ns_entry, "mod_ns", &data);
+    if (data.kadm_handle)
+        kadm5_destroy(data.kadm_handle);
     return ret != 0;
 }
 
@@ -672,15 +693,20 @@ modify_ns_kr(struct modify_namespace_key_rotation_options *opt,
              char **argv)
 {
     krb5_error_code ret = 0;
+    struct mod_data data;
     int i;
 
-    for(i = 0; i < argc; i++) {
+    data.kadm_handle = NULL;
+    data.opt_ns_kr = opt;
+    data.opt_ns = NULL;
+    data.opt = NULL;
+
+    ret = kadm5_dup_context(kadm_handle, &data.kadm_handle);
+    for (i = 0; ret == 0 && i < argc; i++)
        ret = foreach_principal(argv[i], do_mod_ns_kr, "mod_ns", opt);
-       if (ret)
-           break;
-    }
+    if (data.kadm_handle)
+        kadm5_destroy(data.kadm_handle);
     return ret != 0;
-    return 0;
 }
 
 #define princ_realm(P) ((P)->realm)
index 1ae10f1af7caaa30bfe166d8bef3e45e24e59027..5cae3d2c2390505a19d1b8b42fe4d13631efbe1b 100644 (file)
@@ -972,7 +972,7 @@ process_stream(krb5_context contextp,
            INSIST(gctx.ctx == NULL);
 
            gctx.inprogress = 1;
-           fallthrough;
+           HEIM_FALLTHROUGH;
        case RPG_CONTINUE_INIT: {
            gss_name_t src_name = GSS_C_NO_NAME;
            krb5_data in;
index 52f20202e7fb511100118e4edfcf357ee8e54afe..281822a30fc08d941a4cf7b6d67405822e98ed09 100644 (file)
@@ -38,9 +38,148 @@ static kadm5_ret_t check_aliases(kadm5_server_context *,
                                  kadm5_principal_ent_rec *,
                                  kadm5_principal_ent_rec *);
 
+/*
+ * All the iter_cb stuff is about online listing of principals via
+ * kadm5_iter_principals().  Search for "LIST" to see more commentary.
+ */
+struct iter_cb_data {
+    krb5_context context;
+    krb5_auth_context ac;
+    krb5_storage *rsp;
+    kadm5_ret_t ret;
+    size_t n;
+    size_t i;
+    int fd;
+    unsigned int initial:1;
+    unsigned int stop:1;
+};
+
+/*
+ * This function sends the current chunk of principal listing and checks if the
+ * client requested that the listing stop.
+ */
+static int
+iter_cb_send_now(struct iter_cb_data *d)
+{
+    struct timeval tv;
+    krb5_data out;
+
+    krb5_data_zero(&out);
+
+    if (!d->stop) {
+        fd_set fds;
+        int nfds;
+
+        /*
+         * The client can send us one message to interrupt the iteration.
+         *
+         * TODO: Maybe we should have the client send a message every N chunks
+         *       so we can clock the listing and have a chance to receive any
+         *       interrupt message from the client?
+         */
+        FD_ZERO(&fds);
+        FD_SET(d->fd, &fds);
+        tv.tv_sec = 0;
+        tv.tv_usec = 0;
+        nfds = select(d->fd + 1, &fds, NULL, NULL, &tv);
+        if (nfds == -1) {
+            d->ret = errno;
+        } else if (nfds > 0) {
+            /*
+             * And it did.  We'll throw this message away.  It should be a NOP
+             * call, which we'd throw away anyways.  If the client's stop
+             * message arrives after we're done anyways, well, it will be
+             * processed as a NOP and thrown away.
+             */
+            d->stop = 1;
+            d->ret = krb5_read_priv_message(d->context, d->ac, &d->fd, &out);
+            krb5_data_free(&out);
+            if (d->ret == HEIM_ERR_EOF)
+                exit(0);
+        }
+    }
+    d->i = 0;
+    d->ret = krb5_storage_to_data(d->rsp, &out);
+    if (d->ret == 0)
+        d->ret = krb5_write_priv_message(d->context, d->ac, &d->fd, &out);
+    krb5_data_free(&out);
+    krb5_storage_free(d->rsp);
+    if ((d->rsp = krb5_storage_emem()) == NULL)
+        return krb5_enomem(d->context);
+    return d->ret;
+}
+
+static int
+iter_cb(void *cbdata, const char *p)
+{
+    struct iter_cb_data *d = cbdata;
+    krb5_error_code ret = 0;
+    size_t n = d->n;
+
+    /* Convince the compiler that `-(int)d->n' is defined */
+    if (n == 0 || n > INT_MAX)
+        return ERANGE;
+    if (d->rsp == NULL && (d->rsp = krb5_storage_emem()) == NULL)
+        return krb5_enomem(d->context);
+    if (d->i == 0) {
+        /* Every chunk starts with a result code */
+        ret = krb5_store_int32(d->rsp, d->ret);
+        if (ret)
+            return ret;
+        if (d->ret)
+            return ret;
+    }
+    if (d->initial) {
+        /*
+         * We'll send up to `d->n' entries per-write.  We send a negative
+         * number to indicate we accepted the client's proposal that we speak
+         * the online LIST protocol.
+         *
+         * Note that if we're here then we've already placed a result code in
+         * this reply (see above).
+         */
+        d->initial = 0;
+        ret = krb5_store_int32(d->rsp, -(int)n);    /* Princs per-chunk */
+        if (ret == 0)
+            ret = iter_cb_send_now(d);
+        if (ret)
+            return ret;
+        /*
+         * Now that we've sent the acceptance reply, put a result code as the
+         * first thing in the next reply, which will have the first chunk of
+         * the listing.
+         */
+        ret = krb5_store_int32(d->rsp, d->ret);
+        if (ret)
+            return ret;
+        if (d->ret)
+            return ret;
+    }
+
+    if (p) {
+        ret = krb5_store_string(d->rsp, p);
+        d->i++;
+    } else {
+        /*
+         * We get called with `p == NULL' when the listing is done.  This
+         * forces us to iter_cb_send_now(d) below, but also forces us to have a
+         * properly formed reply (i.e., that we have a result code as the first
+         * item), even if the chunk is otherwise empty (`d->i == 0').
+         */
+        d->i = n;
+    }
+
+    if (ret == 0 && d->i == n)
+        ret = iter_cb_send_now(d); /* Chunk finished; send it */
+    if (d->stop)
+        return EINTR;
+    return ret;
+}
+
 static kadm5_ret_t
 kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
-                krb5_data *in, krb5_data *out, int readonly)
+                krb5_data *in, krb5_auth_context ac, int fd,
+                 krb5_data *out, int readonly)
 {
     kadm5_ret_t ret = 0;
     kadm5_ret_t ret_sp = 0;
@@ -81,18 +220,48 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
                                  client, sizeof(client));
     if (ret == 0)
         ret = krb5_ret_int32(sp, &cmd);
-    if (ret)
+    if (ret) {
+        ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
         goto fail;
+    }
 
     switch(cmd){
+    case kadm_nop:{
+        /*
+         * In the future we could use this for versioning.
+         *
+         * We used to respond to NOPs with KADM5_FAILURE.  Now we respond with
+         * zero.  In the future we could send back a protocol version number
+         * and use NOPs for protocol version negotiation.
+         *
+         * In the meantime, this gets called only if a client wants to
+         * interrupt a long-running LIST operation.
+         */
+       op = "NOP";
+       ret = krb5_ret_int32(sp, &tmp);
+        if (ret == 0 && tmp == 0) {
+            /*
+             * Reply not wanted.  This would be a LIST interrupt request.
+             */
+            krb5_storage_free(rsp);
+            krb5_storage_free(sp);
+            return 0;
+        }
+       ret_sp = krb5_store_int32(rsp, ret = 0);
+        break;
+    }
     case kadm_get:{
        op = "GET";
        ret = krb5_ret_principal(sp, &princ);
-       if(ret)
+       if (ret) {
+            ret_sp = krb5_store_int32(rsp, KADM5_UNK_PRINC);
            goto fail;
+        }
        ret = krb5_ret_int32(sp, &mask);
-       if (ret)
+       if (ret) {
+            ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
            goto fail;
+        }
 
        mask |= KADM5_PRINCIPAL;
        krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name));
@@ -100,8 +269,10 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
 
         /* If the caller doesn't have KADM5_PRIV_GET, we're done. */
        ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_GET, princ);
-        if (ret)
+        if (ret) {
+            ret_sp = krb5_store_int32(rsp, ret);
            goto fail;
+        }
 
         /* Then check to see if it is ok to return keys */
         if ((mask & KADM5_KEY_DATA) != 0) {
@@ -129,6 +300,7 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
                  * modes request other things too, so in all likelihood this
                  * heuristic will not hurt any kadmin get uses.
                  */
+                ret_sp = krb5_store_int32(rsp, ret);
                 goto fail;
             }
         }
@@ -147,7 +319,7 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
     case kadm_delete:{
        op = "DELETE";
        if (readonly) {
-            ret = KADM5_READ_ONLY;
+            ret_sp = krb5_store_int32(rsp, ret = KADM5_READ_ONLY);
             goto fail;
         }
        ret = krb5_ret_principal(sp, &princ);
@@ -171,19 +343,23 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
     case kadm_create:{
        op = "CREATE";
        if (readonly) {
-            ret = KADM5_READ_ONLY;
+            ret_sp = krb5_store_int32(rsp, ret = KADM5_READ_ONLY);
             goto fail;
         }
        ret = kadm5_ret_principal_ent(sp, &ent);
-       if(ret)
+       if(ret) {
+            ret_sp = krb5_store_int32(rsp, ret);
            goto fail;
+        }
        ret = krb5_ret_int32(sp, &mask);
        if(ret){
+            ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
            kadm5_free_principal_ent(kadm_handlep, &ent);
            goto fail;
        }
        ret = krb5_ret_string(sp, &password);
        if(ret){
+            ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
            kadm5_free_principal_ent(kadm_handlep, &ent);
            goto fail;
        }
@@ -193,6 +369,7 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
        ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_ADD,
                                          ent.principal);
        if(ret){
+            ret_sp = krb5_store_int32(rsp, ret);
            kadm5_free_principal_ent(kadm_handlep, &ent);
            goto fail;
        }
@@ -203,6 +380,7 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
              */
             ret = check_aliases(contextp, &ent, NULL);
             if (ret) {
+                ret_sp = krb5_store_int32(rsp, KADM5_BAD_PRINCIPAL);
                 kadm5_free_principal_ent(kadm_handlep, &ent);
                 goto fail;
             }
@@ -216,14 +394,17 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
     case kadm_modify:{
        op = "MODIFY";
        if (readonly) {
-            ret = KADM5_READ_ONLY;
+            ret_sp = krb5_store_int32(rsp, ret = KADM5_READ_ONLY);
             goto fail;
         }
        ret = kadm5_ret_principal_ent(sp, &ent);
-       if(ret)
+       if(ret) {
+            ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
            goto fail;
+        }
        ret = krb5_ret_int32(sp, &mask);
        if(ret){
+            ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
            kadm5_free_principal_ent(contextp, &ent);
            goto fail;
        }
@@ -233,6 +414,7 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
        ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_MODIFY,
                                          ent.principal);
        if(ret){
+            ret_sp = krb5_store_int32(rsp, ret);
            kadm5_free_principal_ent(contextp, &ent);
            goto fail;
        }
@@ -245,12 +427,14 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
              */
             ret = kadm5_get_principal(kadm_handlep, ent.principal, &ent_prev, mask);
             if (ret) {
+                ret_sp = krb5_store_int32(rsp, ret);
                 kadm5_free_principal_ent(contextp, &ent);
                 goto fail;
             }
             ret = check_aliases(contextp, &ent, &ent_prev);
             kadm5_free_principal_ent(contextp, &ent_prev);
             if (ret) {
+                ret_sp = krb5_store_int32(rsp, KADM5_BAD_PRINCIPAL);
                 kadm5_free_principal_ent(contextp, &ent);
                 goto fail;
             }
@@ -263,23 +447,25 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
     case kadm_prune:{
         op = "PRUNE";
        if (readonly) {
-            ret = KADM5_READ_ONLY;
+            ret_sp = krb5_store_int32(rsp, ret = KADM5_READ_ONLY);
             goto fail;
         }
         ret = krb5_ret_principal(sp, &princ);
-        if (ret)
-            goto fail;
-        ret = krb5_ret_int32(sp, &kvno);
+        if (ret == 0)
+            ret = krb5_ret_int32(sp, &kvno);
         if (ret == HEIM_ERR_EOF) {
             kvno = 0;
         } else if (ret) {
+            ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
             goto fail;
         }
         krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name));
         krb5_warnx(contextp->context, "%s: %s %s", client, op, name);
         ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_CPW, princ);
-        if (ret)
+        if (ret) {
+            ret_sp = krb5_store_int32(rsp, ret);
             goto fail;
+        }
 
         ret = kadm5_prune_principal(kadm_handlep, princ, kvno);
         ret_sp = krb5_store_int32(rsp, ret);
@@ -288,15 +474,16 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
     case kadm_rename:{
        op = "RENAME";
        if (readonly) {
-            ret = KADM5_READ_ONLY;
+            ret_sp = krb5_store_int32(rsp, ret = KADM5_READ_ONLY);
             goto fail;
         }
        ret = krb5_ret_principal(sp, &princ);
-       if(ret)
-           goto fail;
-       ret = krb5_ret_principal(sp, &princ2);
-       if (ret)
+        if (ret == 0)
+            ret = krb5_ret_principal(sp, &princ2);
+       if (ret) {
+            ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
            goto fail;
+        }
 
        krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name));
        krb5_unparse_name_fixed(contextp->context, princ2,
@@ -321,11 +508,13 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
                                                   princ);
             }
         }
-       if (ret)
+       if (ret) {
+            ret_sp = krb5_store_int32(rsp, ret);
            goto fail;
+        }
 
        ret = kadm5_rename_principal(kadm_handlep, princ, princ2);
-       ret_sp = krb5_store_int32(sp, ret);
+       ret_sp = krb5_store_int32(rsp, ret);
        break;
     }
     case kadm_chpass:{
@@ -333,7 +522,7 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
 
        op = "CHPASS";
        if (readonly) {
-            ret = KADM5_READ_ONLY;
+            ret_sp = krb5_store_int32(rsp, ret = KADM5_READ_ONLY);
             goto fail;
         }
        ret = krb5_ret_principal(sp, &princ);
@@ -348,8 +537,10 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
             if (ret == 0)
                 krb5_warnx(contextp->context, "%s: %s %s", client, op, name);
         }
-       if (ret)
+       if (ret) {
+            ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
            goto fail;
+        }
 
        /*
         * Change password requests are subject to ACLs unless the principal is
@@ -363,8 +554,10 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
                                         "kadmin", "allow_self_change_password", NULL);
        if (!(is_self_cpw && initial && allow_self_cpw)) {
            ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_CPW, princ);
-           if (ret)
+           if (ret) {
+                ret_sp = krb5_store_int32(rsp, ret);
                goto fail;
+            }
        }
 
        ret = kadm5_chpass_principal_3(kadm_handlep, princ, keepold, 0, NULL,
@@ -379,7 +572,7 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
 
        op = "CHPASS_WITH_KEY";
        if (readonly) {
-            ret = KADM5_READ_ONLY;
+            ret_sp = krb5_store_int32(rsp, ret = KADM5_READ_ONLY);
             goto fail;
         }
        ret = krb5_ret_principal(sp, &princ);
@@ -390,18 +583,22 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
            if (ret == HEIM_ERR_EOF)
                ret = 0;
        }
-       if (ret)
+       if (ret) {
+            ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
            goto fail;
+        }
 
        /* n_key_data will be squeezed into an int16_t below. */
        if (n_key_data < 0 || n_key_data >= 1 << 16 ||
            (size_t)n_key_data > UINT_MAX/sizeof(*key_data)) {
+            ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
            ret = ERANGE;
            goto fail;
        }
 
        key_data = malloc (n_key_data * sizeof(*key_data));
        if (key_data == NULL && n_key_data != 0) {
+            ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
            ret = krb5_enomem(contextp->context);
            goto fail;
        }
@@ -413,6 +610,7 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
 
                kadm5_free_key_data (contextp, &dummy, key_data);
                free (key_data);
+                ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
                goto fail;
            }
        }
@@ -432,6 +630,7 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
 
            kadm5_free_key_data (contextp, &dummy, key_data);
            free (key_data);
+            ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
            goto fail;
        }
        ret = kadm5_chpass_principal_with_key_3(kadm_handlep, princ, keepold,
@@ -449,12 +648,14 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
 
        op = "RANDKEY";
        if (readonly) {
-            ret = KADM5_READ_ONLY;
+            ret_sp = krb5_store_int32(rsp, ret = KADM5_READ_ONLY);
             goto fail;
         }
        ret = krb5_ret_principal(sp, &princ);
-       if (ret)
+       if (ret) {
+            ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
            goto fail;
+        }
        krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name));
        krb5_warnx(contextp->context, "%s: %s %s", client, op, name);
        /*
@@ -470,16 +671,20 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
        else
            ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_CPW, princ);
 
-       if (ret)
+       if (ret) {
+            ret_sp = krb5_store_int32(rsp, ret);
            goto fail;
+        }
 
        /*
         * See comments in kadm5_c_randkey_principal() regarding the
         * protocol.
         */
        ret = krb5_ret_int32(sp, &keepold);
-       if (ret != 0 && ret != HEIM_ERR_EOF)
+       if (ret != 0 && ret != HEIM_ERR_EOF) {
+            ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
            goto fail;
+        }
 
        ret = krb5_ret_int32(sp, &n_ks_tuple);
        if (ret == HEIM_ERR_EOF) {
@@ -497,15 +702,19 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
                                            &n, &ks_tuple);
            n_ks_tuple = n;
         }
-        if (ret != 0)
+        if (ret != 0) {
+            ret_sp = krb5_store_int32(rsp, KADM5_FAILURE); /* XXX */
            goto fail;
+        }
 
         if (n_ks_tuple < 0) {
+            ret_sp = krb5_store_int32(rsp, KADM5_FAILURE); /* XXX */
             ret = EOVERFLOW;
             goto fail;
         }
        free(ks_tuple);
         if ((ks_tuple = calloc(n_ks_tuple, sizeof (*ks_tuple))) == NULL) {
+            ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
             ret = errno;
             goto fail;
         }
@@ -513,11 +722,13 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
         for (i = 0; i < n_ks_tuple; i++) {
             ret = krb5_ret_int32(sp, &ks_tuple[i].ks_enctype);
             if (ret != 0) {
+                ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
                 free(ks_tuple);
                 goto fail;
             }
             ret = krb5_ret_int32(sp, &ks_tuple[i].ks_salttype);
             if (ret != 0) {
+                ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
                 free(ks_tuple);
                 goto fail;
             }
@@ -543,43 +754,119 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
        uint32_t privs;
        ret = kadm5_get_privs(kadm_handlep, &privs);
        if (ret == 0)
-           ret_sp = krb5_store_uint32(sp, privs);
+           ret_sp = krb5_store_uint32(rsp, privs);
        break;
     }
     case kadm_get_princs:{
        op = "LIST";
        ret = krb5_ret_int32(sp, &tmp);
-       if(ret)
+       if (ret) {
+            ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
            goto fail;
-       if(tmp){
+        }
+        /* See kadm5_c_iter_principals() */
+       if (tmp == 0x55555555) {
+            /* Want online iteration */
+           ret = krb5_ret_string(sp, &expression);
+           if (ret) {
+                ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
+                goto fail;
+            }
+            if (expression[0] == '\0') {
+                free(expression);
+                expression = NULL;
+            }
+        } else if (tmp) {
            ret = krb5_ret_string(sp, &expression);
-           if(ret)
+           if (ret) {
+                ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
                goto fail;
+            }
        }else
            expression = NULL;
        krb5_warnx(contextp->context, "%s: %s %s", client, op,
                   expression ? expression : "*");
        ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_LIST, NULL);
        if(ret){
+            ret_sp = krb5_store_int32(rsp, ret);
            free(expression);
            goto fail;
        }
-       ret = kadm5_get_principals(kadm_handlep, expression, &princs, &n_princs);
-       free(expression);
-       ret_sp = krb5_store_int32(rsp, ret);
-       if (ret == 0) {
-           int i;
+        if (fd > -1 && tmp == 0x55555555) {
+            struct iter_cb_data iter_cbdata;
+            int n;
 
-           ret_sp = krb5_store_int32(sp, n_princs);
-           for (i = 0; ret_sp == 0 && i < n_princs; i++)
-               ret_sp = krb5_store_string(sp, princs[i]);
-           kadm5_free_name_list(kadm_handlep, princs, &n_princs);
-       }
+            /*
+             * The client proposes that we speak the online variation of LIST
+             * by sending a magic value in the int32 that is meant to be a
+             * boolean for "an expression follows".  The client must send an
+             * expression in this case because the server might be an old one,
+             * so even if the caller to kadm5_get/iter_principals() passed NULL
+             * for the expression, the client must send something ("*").
+             *
+             * The list of principals will be streamed in multiple replies.
+             *
+             * The first reply will have just a return code and a negative
+             * count of maximum number of names per-subsequent reply.  See
+             * `iter_cb()'.
+             *
+             * The second reply, third, .., nth replies will have a return code
+             * followed by 50 names, except the last reply must have fewer than
+             * 50 names -zero if need be- so the client can deterministically
+             * notice the end of the stream.
+             */
+
+            n = list_chunk_size;
+            if (n < 0)
+                n = krb5_config_get_int_default(contextp->context, NULL, -1,
+                                                "kadmin", "list_chunk_size", NULL);
+            if (n < 0)
+                n = 50;
+            if (n > 500)
+                n = 500;
+            if ((iter_cbdata.rsp = krb5_storage_emem()) == NULL) {
+                ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
+                ret = krb5_enomem(contextp->context);
+                goto fail;
+            }
+            iter_cbdata.context = contextp->context;
+            iter_cbdata.initial = 1;
+            iter_cbdata.stop = 0;
+            iter_cbdata.ret = 0;
+            iter_cbdata.ac = ac;
+            iter_cbdata.fd = fd;
+            iter_cbdata.n = n;
+            iter_cbdata.i = 0;
+
+            /*
+             * All sending of replies will happen in iter_cb, except for the
+             * final chunk with the final result code.
+             */
+            iter_cbdata.ret = kadm5_iter_principals(kadm_handlep, expression,
+                                                     iter_cb, &iter_cbdata);
+            /* Send terminating chunk */
+            iter_cb(&iter_cbdata, NULL);
+            /* Final result */
+            ret = krb5_store_int32(rsp, iter_cbdata.ret);
+            krb5_storage_free(iter_cbdata.rsp);
+        } else {
+            ret = kadm5_get_principals(kadm_handlep, expression, &princs, &n_princs);
+            ret_sp = krb5_store_int32(rsp, ret);
+            if (ret == 0 && ret_sp == 0) {
+                int i;
+
+                ret_sp = krb5_store_int32(rsp, n_princs);
+                for (i = 0; ret_sp == 0 && i < n_princs; i++)
+                    ret_sp = krb5_store_string(rsp, princs[i]);
+                kadm5_free_name_list(kadm_handlep, princs, &n_princs);
+            }
+        }
+        free(expression);
        break;
     }
     default:
        krb5_warnx(contextp->context, "%s: UNKNOWN OP %d", client, cmd);
-       ret_sp = krb5_store_int32(sp, KADM5_FAILURE);
+       ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
        break;
     }
 
@@ -744,11 +1031,13 @@ v5_loop (krb5_context contextp,
        if(ret)
            krb5_err(contextp, 1, ret, "krb5_read_priv_message");
        doing_useful_work = 1;
-       ret = kadmind_dispatch(kadm_handlep, initial, &in, &out, readonly);
+        ret = kadmind_dispatch(kadm_handlep, initial, &in, ac, fd, &out,
+                               readonly);
        if (ret)
            krb5_err(contextp, 1, ret, "kadmind_dispatch");
        krb5_data_free(&in);
-       ret = krb5_write_priv_message(contextp, ac, &fd, &out);
+        if (out.length)
+            ret = krb5_write_priv_message(contextp, ac, &fd, &out);
        krb5_data_free(&out);
        if(ret)
            krb5_err(contextp, 1, ret, "krb5_write_priv_message");
index 720d9d3b759b592f8a3e95b498a28f4da14d8fd1..fda1e982a9b1adaf76f866d66b66abd4715e3548 100644 (file)
@@ -47,6 +47,7 @@ get_response(const char *prompt, const char *def, char *buf, size_t len);
  */
 
 struct units kdb_attrs[] = {
+    { "no-auth-data-reqd",     KRB5_KDB_NO_AUTH_DATA_REQUIRED },
     { "disallow-client",       KRB5_KDB_DISALLOW_CLIENT },
     { "virtual",               KRB5_KDB_VIRTUAL },
     { "virtual-keys",          KRB5_KDB_VIRTUAL_KEYS },
@@ -69,7 +70,6 @@ struct units kdb_attrs[] = {
     { "disallow-tgt-based",    KRB5_KDB_DISALLOW_TGT_BASED },
     { "disallow-forwardable",  KRB5_KDB_DISALLOW_FORWARDABLE },
     { "disallow-postdated",    KRB5_KDB_DISALLOW_POSTDATED },
-    { "no-auth-data-reqd",     KRB5_KDB_NO_AUTH_DATA_REQUIRED },
     { NULL, 0 }
 };
 
@@ -353,7 +353,14 @@ edit_timet (const char *prompt, krb5_timestamp *value, int *mask, int bit)
 void
 deltat2str(unsigned t, char *str, size_t len)
 {
-    if(t == 0 || t == INT_MAX)
+    /*
+     * A time delta in kadmin context is a positive number, and there's no
+     * point to it being possibly as large as 2^64 -1, so we use unsigned
+     * instead of a more generally appropriate type for time deltas (which
+     * conceptually can be negative, which in kadmin context there's no need
+     * for).
+     */
+    if (t == 0 || t > INT_MAX)
        snprintf(str, len, "unlimited");
     else
        unparse_time(t, str, len);
@@ -370,6 +377,15 @@ str2deltat(const char *str, krb5_deltat *delta)
     int res;
 
     if(strcasecmp(str, "unlimited") == 0) {
+        /*
+         * Using zero to mean "unlimited" is unfortunate.  We should use
+         * `UINT_MAX'.  However, we've had this assumption that zero means
+         * unlimited, so there are HDB entries with present-but-zero max-life
+         * and max-renew-life.
+         *
+         * We could switch to using `UINT_MAX' or `UINT64_MAX' for "unlimited",
+         * but we'd have to continue to treat `0' as special for some time.
+         */
        *delta = 0;
        return 0;
     }
@@ -589,6 +605,32 @@ is_expression(const char *string)
     return 0;
 }
 
+struct foreach_principal_data {
+    const char *funcname;
+    int (*func)(krb5_principal, void *);
+    void *data;
+};
+
+static int
+foreach_principal_cb(void *data, const char *p)
+{
+    struct foreach_principal_data *d = data;
+    krb5_principal princ;
+    krb5_error_code ret;
+
+    ret = krb5_parse_name(context, p, &princ);
+    if (ret)
+        return ret;
+
+    ret = d->func(princ, d->data);
+    krb5_free_principal(context, princ);
+    if (ret) {
+        krb5_warn(context, ret, "%s %s", d->funcname, p);
+        krb5_clear_error_message(context);
+    }
+    return ret;
+}
+
 /*
  * Loop over all principals matching exp.  If any of calls to `func'
  * failes, the first error is returned when all principals are
@@ -600,52 +642,66 @@ foreach_principal(const char *exp_str,
                  const char *funcname,
                  void *data)
 {
-    char **princs = NULL;
-    int num_princs = 0;
-    int i;
-    krb5_error_code saved_ret = 0, ret = 0;
-    krb5_principal princ_ent;
+    struct foreach_principal_data d;
+    krb5_error_code ret;
+    krb5_principal p;
     int is_expr;
+    int go_slow =
+        secure_getenv("KADMIN_USE_GET_PRINCIPALS") != NULL &&
+        *secure_getenv("KADMIN_USE_GET_PRINCIPALS") != '\0';
 
     /* if this isn't an expression, there is no point in wading
        through the whole database looking for matches */
     is_expr = is_expression(exp_str);
-    if(is_expr)
-       ret = kadm5_get_principals(kadm_handle, exp_str, &princs, &num_princs);
-    if(!is_expr || ret == KADM5_AUTH_LIST) {
-       /* we might be able to perform the requested opreration even
-           if we're not allowed to list principals */
-       num_princs = 1;
-       princs = malloc(sizeof(*princs));
-       if(princs == NULL)
-           return ENOMEM;
-       princs[0] = strdup(exp_str);
-       if(princs[0] == NULL){
-           free(princs);
-           return ENOMEM;
-       }
-    } else if(ret) {
-       krb5_warn(context, ret, "kadm5_get_principals");
-       return ret;
-    }
-    for(i = 0; i < num_princs; i++) {
-       ret = krb5_parse_name(context, princs[i], &princ_ent);
-       if(ret){
-           krb5_warn(context, ret, "krb5_parse_name(%s)", princs[i]);
-           continue;
-       }
-       ret = (*func)(princ_ent, data);
-       if(ret) {
-           krb5_warn(context, ret, "%s %s", funcname, princs[i]);
-           krb5_clear_error_message(context);
-           if (saved_ret == 0)
-               saved_ret = ret;
-       }
-       krb5_free_principal(context, princ_ent);
+
+    d.funcname = funcname;
+    d.func = func;
+    d.data = data;
+
+    if (is_expr && !go_slow) {
+       ret = kadm5_iter_principals(kadm_handle, exp_str,
+                                    foreach_principal_cb, &d);
+        if (ret == 0)
+            return 0;
+        if (ret != KADM5_AUTH_LIST) {
+            krb5_warn(context, ret, "kadm5_iter_principals");
+            return ret;
+        }
+    } else if (is_expr) {
+        char **princs = NULL;
+        int count = 0;
+
+        /*
+         * This is just for testing, and maybe in case there are HDB backends
+         * that are not re-entrant (LDAP?).
+         */
+        ret = kadm5_get_principals(kadm_handle, exp_str, &princs, &count);
+        if (ret == 0 && count > 0) {
+            int i;
+
+            for (i = 0; ret == 0 && i < count; i++)
+                ret = foreach_principal_cb(&d, princs[i]);
+            kadm5_free_name_list(kadm_handle, princs, &count);
+            return ret;
+        }
+        if (ret != KADM5_AUTH_LIST) {
+            krb5_warn(context, ret, "kadm5_iter_principals");
+            return ret;
+        }
+    }
+    /* we might be able to perform the requested opreration even
+       if we're not allowed to list principals */
+    ret = krb5_parse_name(context, exp_str, &p);
+    if (ret) {
+        krb5_warn(context, ret, "krb5_parse_name(%s)", exp_str);
+        return ret;
+    }
+    ret = (*func)(p, data);
+    if (ret) {
+        krb5_warn(context, ret, "%s %s", funcname, exp_str);
+        krb5_clear_error_message(context);
     }
-    if (ret == 0 && saved_ret != 0)
-       ret = saved_ret;
-    kadm5_free_name_list(kadm_handle, princs, &num_princs);
+    krb5_free_principal(context, p);
     return ret;
 }
 
index be4bce6af9206c78feafa427b760ec96e668fc71..1a2b81ceb333ed5d3ce02de2f753725444bf76a1 100644 (file)
@@ -45,6 +45,7 @@ static char *max_request_str; /* `max_request' as a string */
 
 int detach_from_console = -1;
 int daemon_child = -1;
+int automatic_renewal = -1;
 
 static const char *system_cache_name = NULL;
 static const char *system_keytab = NULL;
@@ -93,6 +94,10 @@ static struct getargs args[] = {
         "daemon-child",       0 ,      arg_integer, &daemon_child,
         "private argument, do not use", NULL
     },
+    {
+       "automatic-renewal",    0 , arg_negative_flag, &automatic_renewal,
+       "disable automatic TGT renewal", NULL
+    },
     {  "help",         'h',    arg_flag,   &help_flag, NULL, NULL },
     {
        "system-principal",     'k',    arg_string,     &system_principal,
@@ -390,6 +395,13 @@ kcm_configure(int argc, char **argv)
            krb5_err(kcm_context, 1, ret, "initializing system ccache");
     }
 
+    if(automatic_renewal == -1)
+       automatic_renewal = krb5_config_get_bool_default(kcm_context, NULL,
+                                                        TRUE,
+                                                        "kcm",
+                                                        "automatic_renewal",
+                                                        NULL);
+
     if(detach_from_console == -1)
        detach_from_console = krb5_config_get_bool_default(kcm_context, NULL,
                                                           FALSE,
index cbbe58ac29df6ee92c83c1e724db82d6074a444e..8b78c10f07d74253a7565013679e8fcd6a6be3a4 100644 (file)
@@ -220,7 +220,7 @@ kcm_ccache_make_default_event(krb5_context context,
        event->fire_time = time(NULL); /* right away */
        event->action = KCM_EVENT_ACQUIRE_CREDS;
     } else if (is_primary_credential_p(context, ccache, newcred)) {
-       if (newcred->flags.b.renewable) {
+       if (automatic_renewal && newcred->flags.b.renewable) {
            event->action = KCM_EVENT_RENEW_CREDS;
            ccache->flags |= KCM_FLAGS_RENEWABLE;
        } else {
index c337384972d40e29615aaabb6ebe6c5430836394..dc0110448ac7223cbd1e02333019664e8a5b645a 100644 (file)
@@ -169,6 +169,7 @@ extern sig_atomic_t exit_flag;
 extern int name_constraints;
 extern int detach_from_console;
 extern int daemon_child;
+extern int automatic_renewal;
 extern int launchd_flag;
 extern int disallow_getting_krbtgt;
 
index c7f57251f7c8aedbef0a58603b4f7ed51756584b..48248d8248b04248a95b78f19e09b01c277e63a3 100644 (file)
@@ -44,6 +44,7 @@ bx509d_LDADD =        -ldl \
                 $(MICROHTTPD_LIBS) \
                 $(LIB_roken) \
                 $(LIB_heimbase) \
+                $(LIB_hcrypto) \
                 $(top_builddir)/lib/sl/libsl.la \
                 $(top_builddir)/lib/asn1/libasn1.la \
                 $(top_builddir)/lib/krb5/libkrb5.la \
index 512d0545ed67e23a91121f68fdfabd2aeca3f7eb..f94015568b75ce9225430fe407df3c30148a199e 100644 (file)
 .Op Fl Fl version
 .Op Fl H Ar HOSTNAME
 .Op Fl d | Fl Fl daemon
-.Op Fl Fl daemon-child
+.Op Fl Fl allow-GET
+.Op Fl Fl no-allow-GET
+.Op Fl Fl csrf-protection-type= Ns Ar CSRF-PROTECTION-TYPE
+.Op Fl Fl csrf-header= Ns Ar HEADER-NAME
+.Op Fl Fl csrf-key-file= Ns Ar FILE
 .Op Fl Fl reverse-proxied
 .Op Fl p Ar port number (default: 443)
 .Op Fl Fl cache-dir= Ns Ar DIRECTORY
 .Oc
 .Sh DESCRIPTION
 Serves RESTful (HTTPS) GETs of
-.Ar /bx509 and
-.Ar /bnegotiate ,
-end-points
-performing corresponding kx509 and, possibly, PKINIT requests
-to the KDCs of the requested realms (or just the given REALM).
+.Ar /get-cert ,
+.Ar /get-tgt ,
+.Ar /get-tgts ,
+and
+.Ar /get-negotiate-token ,
+end-points that implement various experimental Heimdal features:
+.Bl -bullet -compact -offset indent
+.It
+.Li an online CA service over HTTPS,
+.It
+.Li a kinit-over-HTTPS service, and
+.It
+.Li a Negotiate token over HTTPS service.
+.El
+.Pp
+As well, a
+.Ar /health
+service can be used for checking service status.
 .Pp
 Supported options:
 .Bl -tag -width Ds
@@ -75,6 +92,64 @@ Print version.
 .Xc
 Expected audience(s) of bearer tokens (i.e., acceptor name).
 .It Xo
+.Fl Fl allow-GET
+.Xc
+If given, then HTTP GET will be allowed for the various
+end-points other than
+.Ar /health .
+Otherwise only HEAD and POST will be allowed.
+By default GETs are allowed, but this will change soon.
+.It Xo
+.Fl Fl no-allow-GET
+.Xc
+If given then HTTP GETs will be rejected for the various
+end-points other than
+.Ar /health .
+.It Xo
+.Fl Fl csrf-protection-type= Ns Ar CSRF-PROTECTION-TYPE
+.Xc
+Possible values of
+.Ar CSRF-PROTECTION-TYPE
+are
+.Bl -bullet -compact -offset indent
+.It
+.Li GET-with-header
+.It
+.Li GET-with-token
+.It
+.Li POST-with-header
+.It
+.Li POST-with-token
+.El
+This may be given multiple times.
+The default is to require CSRF tokens for POST requests, and to
+require neither a non-simple header nor a CSRF token for GET
+requests.
+.Pp
+See
+.Sx CROSS-SITE REQUEST FORGERY PROTECTION .
+.It Xo
+.Fl Fl csrf-header= Ns Ar HEADER-NAME
+.Xc
+If given, then all requests other than to the
+.Ar /health
+service must have the given request
+.Ar HEADER-NAME
+set (the value is irrelevant).
+The
+.Ar HEADER-NAME
+must be a request header name such that a request having it makes
+it not a
+.Dq simple
+request (see the Cross-Origin Resource Sharing specification).
+Defaults to
+.Ar X-CSRF .
+.It Xo
+.Fl Fl csrf-key-file= Ns Ar FILE
+.Xc
+If given, this file must contain a 16 byte binary key for keying
+the HMAC used in CSRF token construction.
+.It Xo
 .Fl d ,
 .Fl Fl daemon
 .Xc
@@ -82,7 +157,8 @@ Detach from TTY and run in the background.
 .It Xo
 .Fl Fl reverse-proxied
 .Xc
-Serves HTTP instead of HTTPS, accepting only looped-back connections.
+Serves HTTP instead of HTTPS, accepting only looped-back
+connections.
 .It Xo
 .Fl p Ar port number (default: 443)
 .Xc
@@ -90,29 +166,106 @@ PORT
 .It Xo
 .Fl Fl cache-dir= Ns Ar DIRECTORY
 .Xc
-Directory for various caches.  If not specified then a temporary directory will
-be made.
+Directory for various caches.
+If not specified then a temporary directory will be made.
 .It Xo
 .Fl Fl cert= Ns Ar HX509-STORE
 .Xc
-Certificate file path (PEM) for HTTPS service.  May contain private key as
-well.
+Certificate file path (PEM) for HTTPS service.
+May contain private key as well.
 .It Xo
 .Fl Fl private-key= Ns Ar HX509-STORE
 .Xc
-Private key file path (PEM), if the private key is not stored along with the
-certificiate.
+Private key file path (PEM), if the private key is not stored
+along with the certificiate.
 .It Xo
 .Fl t ,
 .Fl Fl thread-per-client
 .Xc
-Uses a thread per-client instead of as many threads as there are CPUs.
+Uses a thread per-client instead of as many threads as there are
+CPUs.
 .It Xo
 .Fl v ,
 .Fl Fl verbose= Ns Ar run verbosely
 .Xc
 verbose
 .El
+.Sh HTTP APIS
+All HTTP APIs served by this program accept POSTs, with all
+request parameters given as URI query parameters and/or as
+form data in the POST request body, in either
+.Ar application/x-www-form-urlencoded
+or
+.Ar multipart/formdata .
+If request parameters are given both as URI query parameters
+and as POST forms, then they are merged into a set.
+.Pp
+If GETs are enabled, then request parameters must be supplied
+only as URI query parameters, as GET requests do not have request
+bodies.
+.Pp
+URI query parameters must be of the form
+.Ar param0=value&param1=value...
+.Pp
+Some request parameters can only have one value.
+If multiple values are given for such parameters, then either an
+error will be produced, or only the first URI query parameter
+value will be used, or the first POST form data parameter will be
+used.
+Other request parameters can have multiple values.
+See below.
+.Sh CROSS-SITE REQUEST FORGERY PROTECTION
+.Em None
+of the resources service by this service are intended to be
+executed by web pages.
+.Pp
+All the resources provided by this service are
+.Dq safe
+in the sense that they do not change server-side state besides
+logging, and in that they are idempotent, but they are
+only safe to execute
+.Em if and only if
+the requesting party is trusted to see the response.
+Since none of these resources are intended to be used from web
+pages, it is important that web pages not be able to execute them
+.Em and
+observe the responses.
+.Pp
+In a web browser context, pages from other origins will be able
+to attempt requests to this service, but should never be able to
+see the responses because browsers normally wouldn't allow that.
+Nonetheless, anti cross site request forgery (CSRF) protection
+may be desirable.
+.Pp
+This service provides the following CSRF protection features:
+.Bl -tag -width Ds -offset indent
+.It requests are rejected if they have a
+.Dq Referer
+(except the experimental /get-negotiate-token end-point)
+.It the service can be configured to require a header that would make the
+request not Dq simple
+.It GETs can be disabled (see options), thus requiring POSTs
+.It GETs can be required to have a CSRF token (see below)
+.It POSTs can be required to have a CSRF token
+.El
+.Pp
+The experimental
+.Ar /get-negotiate-token
+end-point, however, always accepts
+.Dq Referer
+requests.
+.Pp
+To obtain a CSRF token, first execute the request without the
+CSRF token, and the resulting error
+response will include a
+.Ar X-CSRF-Token
+response header.
+.Pp
+To execute a request with a CSRF token, first obtain a CSRF token
+as described above, then copy the token to the request as the
+value of the request's
+.Ar X-CSRF-Token
+header.
 .Sh ONLINE CERTIFICATION AUTHORITY HTTP API
 This service provides an HTTP-based Certification Authority (CA).
 CA credentials and configuration are specified in the
@@ -128,8 +281,8 @@ with the base-63 encoding of a DER encoding of a PKCS#10
 .Ar CertificationRequest
 (Certificate Signing Request, or CSR) in a
 .Ar csr
-required query parameter.
-In a successful query, the response body will contain a PEM
+required request parameter.
+In a successful request, the response body will contain a PEM
 encoded end entity certificate and certification chain.
 .Pp
 Or
@@ -146,9 +299,9 @@ Unauthorized requests will elicit a 403 response.
 .Pp
 Subject Alternative Names (SANs) and Extended Key Usage values
 may be requested, both in-band in the CSR as a requested
-extensions attribute, and/or via optional query parameters.
+extensions attribute, and/or via optional request parameters.
 .Pp
-Supported query parameters (separated by ampersands)
+Supported request parameters:
 .Bl -tag -width Ds -offset indent
 .It Li csr = Va base64-encoded-DER-encoded-CSR
 .It Li dNSName = Va hostname
@@ -178,20 +331,20 @@ of
 .Ar /get-negotiate-token
 with a
 .Ar target = Ar service@host
-query parameter.
+request parameter.
 .Pp
-In a successful query, the response body will contain a Negotiate
-token for the authenticated client principal to the requested
-target.
+In a successful request, the response body will contain a
+Negotiate token for the authenticated client principal to the
+requested target.
 .Pp
 Authentication is required.
 Unauthenticated requests will elicit a 401 response.
 .Pp
 Subject Alternative Names (SANs) and Extended Key Usage values
 may be requested, both in-band in the CSR as a requested
-extensions attribute, and/or via optional query parameters.
+extensions attribute, and/or via optional request parameters.
 .Pp
-Supported query parameters (separated by ampersands)
+Supported request parameters:
 .Bl -tag -width Ds -offset indent
 .It Li target = Va service@hostname
 .It Li redirect = Va URI
@@ -221,13 +374,14 @@ The protocol consists of a
 of
 .Ar /get-tgt .
 .Pp
-Supported query parameters (separated by ampersands)
+Supported request parameters:
 .Bl -tag -width Ds -offset indent
 .It Li cname = Va principal-name
 .It Li address = Va IP-address
+.It Li lifetime = Va relative-time
 .El
 .Pp
-In a successful query, the response body will contain a TGT and
+In a successful request, the response body will contain a TGT and
 its session key encoded as a "ccache" file contents.
 .Pp
 Authentication is required.
@@ -239,13 +393,14 @@ same as for
 by the authenticated client principal to get a certificate with
 a PKINIT SAN for itself or the requested principal if a
 .Va cname
-query parameter was included.
+request parameter was included.
 .Pp
 Unauthorized requests will elicit a 403 response.
 .Pp
-Requested IP addresses will be added to the issued TGT if allowed.
-The IP address of the client will be included if address-less TGTs
-are not allowed.
+Requested IP addresses will be added to the issued TGT if
+allowed.
+The IP address of the client will be included if address-less
+TGTs are not allowed.
 See the
 .Va [get-tgt]
 section of
@@ -257,6 +412,48 @@ end-point, but as configured in the
 .Va [get-tgt]
 section of
 .Xr krb5.conf 5 .
+.Sh BATCH TGT HTTP API
+Some sites may have special users that operate batch jobs systems
+and that can impersonate many others by obtaining TGTs for them,
+and which
+.Dq prestash
+credentials for those users in their credentials caches.
+To support these sytems, a
+.Ar GET
+of
+.Ar /get-tgts
+with multiple
+.Ar cname
+request parameters will return those principals' TGTs (if the
+caller is authorized).
+.Pp
+This is similar to the
+.Ar /get-tgt
+end-point, but a) multiple
+.Ar cname
+request parameter values may be given, and b) the caller's
+principal name is not used as a default for the
+.Ar cname
+request parameter.
+The
+.Ar address
+and
+.Ar lifetime
+request parameters are honored.
+.Pp
+For successful
+.Ar GETs
+the response body is a sequence of JSON texts each of which is a
+JSON object with two keys:
+.Bl -tag -width Ds -offset indent
+.It Ar ccache
+with a base64-encoded FILE-type ccache;
+.It Ar name
+the name of the principal whose credentials are in that ccache.
+.El
+.Sh NOTES
+A future release may split all these end-points into separate
+services.
 .Sh ENVIRONMENT
 .Bl -tag -width Ds
 .It Ev KRB5_CONFIG
index 064c424b7c29fa3e76997c0af737edc50871b0ca..4d1b694a914657b24f4e11e48aa24c3a8950b364 100644 (file)
 
 /*
  * This file implements a RESTful HTTPS API to an online CA, as well as an
- * HTTP/Negotiate token issuer.
+ * HTTP/Negotiate token issuer, as well as a way to get TGTs.
  *
- * Users are authenticated with bearer tokens.
+ * Users are authenticated with Negotiate and/or Bearer.
  *
- * This is essentially a RESTful online CA sharing code with the KDC's kx509
- * online CA, and also a proxy for PKINIT and GSS-API (Negotiate).
+ * This is essentially a RESTful online CA sharing some code with the KDC's
+ * kx509 online CA, and also a proxy for PKINIT and GSS-API (Negotiate).
  *
- * To get a key certified:
- *
- *  GET /bx509?csr=<base64-encoded-PKCS#10-CSR>
- *
- * To get an HTTP/Negotiate token:
- *
- *  GET /bnegotiate?target=<acceptor-principal>
- *
- * which, if authorized, produces a Negotiate token (base64-encoded, as
- * expected, with the "Negotiate " prefix, ready to be put in an Authorization:
- * header).
+ * See the manual page for HTTP API details.
  *
  * TBD:
  *  - rewrite to not use libmicrohttpd but an alternative more appropriate to
  *    Heimdal's license (though libmicrohttpd will do)
- *  - /bx509 should include the certificate chain
- *  - /bx509 should support HTTP/Negotiate
  *  - there should be an end-point for fetching an issuer's chain
- *  - maybe add /bkrb5 which returns a KRB-CRED with the user's TGT
  *
  * NOTES:
  *  - We use krb5_error_code values as much as possible.  Where we need to use
  *    (MHD_NO is an ENOMEM-cannot-even-make-a-static-503-response level event.)
  */
 
+/*
+ * Theory of operation:
+ *
+ *  - We use libmicrohttpd (MHD) for the HTTP(S) implementation.
+ *
+ *  - MHD has an online request processing model:
+ *
+ *     - all requests are handled via the `dh' and `dh_cls' closure arguments
+ *       of `MHD_start_daemon()'; ours is called `route()'
+ *
+ *     - `dh' is called N+1 times:
+ *        - once to allocate a request context
+ *        - once for every N chunks of request body
+ *        - once to process the request and produce a response
+ *
+ *     - the response cannot begin to be produced before consuming the whole
+ *       request body (for requests that have a body)
+ *       (this seems like a bug in MHD)
+ *
+ *     - the response body can be produced over multiple calls (i.e., in an
+ *       online manner)
+ *
+ *  - Our `route()' processes any POST request body form data / multipart by
+ *    treating all the key/value pairs as if they had been additional URI query
+ *    parameters.
+ *
+ *  - Then `route()' calls a handler appropriate to the URI local-part with the
+ *    request context, and the handler produces a response in one call.
+ *
+ *    I.e., we turn the online MHD request processing into not-online.  Our
+ *    handlers are presented with complete requests and must produce complete
+ *    responses in one call.
+ *
+ *  - `route()' also does any authentication and CSRF protection so that the
+ *    request handlers don't have to.
+ *
+ * This non-online request handling approach works for most everything we want
+ * to do.  However, for /get-tgts with very large numbers of principals, we
+ * might have to revisit this, using MHD_create_response_from_callback() or
+ * MHD_create_response_from_pipe() (and a thread to do the actual work of
+ * producing the body) instead of MHD_create_response_from_buffer().
+ */
+
 #define _XOPEN_SOURCE_EXTENDED  1
 #define _DEFAULT_SOURCE  1
 #define _BSD_SOURCE  1
@@ -128,20 +158,40 @@ typedef enum MHD_Result heim_mhd_result;
 
 enum k5_creds_kind { K5_CREDS_EPHEMERAL, K5_CREDS_CACHED };
 
+/*
+ * This is to keep track of memory we need to free, mainly because we had to
+ * duplicate data from the MHD POST form data processor.
+ */
+struct free_tend_list {
+    void *freeme1;
+    void *freeme2;
+    struct free_tend_list *next;
+};
+
+/* Per-request context data structure */
 typedef struct bx509_request_desc {
+    /* Common elements for Heimdal request/response services */
     HEIM_SVC_REQUEST_DESC_COMMON_ELEMENTS;
 
     struct MHD_Connection *connection;
+    struct MHD_PostProcessor *pp;
+    struct MHD_Response *response;
     krb5_times token_times;
     time_t req_life;
     hx509_request req;
+    struct free_tend_list *free_list;
     const char *for_cname;
     const char *target;
     const char *redir;
+    const char *method;
+    size_t post_data_size;
     enum k5_creds_kind cckind;
     char *pkix_store;
+    char *tgts_filename;
+    FILE *tgts;
     char *ccname;
     char *freeme1;
+    char *csrf_token;
     krb5_addresses tgt_addresses; /* For /get-tgt */
     char frombuf[128];
 } *bx509_request_desc;
@@ -214,7 +264,17 @@ get_krb5_context(krb5_context *contextp)
     return *contextp ? 0 : ENOMEM;
 }
 
+typedef enum {
+    CSRF_PROT_UNSPEC            = 0,
+    CSRF_PROT_GET_WITH_HEADER   = 1,
+    CSRF_PROT_GET_WITH_TOKEN    = 2,
+    CSRF_PROT_POST_WITH_HEADER  = 8,
+    CSRF_PROT_POST_WITH_TOKEN   = 16,
+} csrf_protection_type;
+
+static csrf_protection_type csrf_prot_type = CSRF_PROT_UNSPEC;
 static int port = -1;
+static int allow_GET_flag = -1;
 static int help_flag;
 static int daemonize;
 static int daemon_child_fd = -1;
@@ -223,11 +283,16 @@ static int version_flag;
 static int reverse_proxied_flag;
 static int thread_per_client_flag;
 struct getarg_strings audiences;
+static getarg_strings csrf_prot_type_strs;
+static const char *csrf_header = "X-CSRF";
 static const char *cert_file;
 static const char *priv_key_file;
 static const char *cache_dir;
+static const char *csrf_key_file;
 static char *impersonation_key_fn;
 
+static char csrf_key[16];
+
 static krb5_error_code resp(struct bx509_request_desc *, int,
                             enum MHD_ResponseMemoryMode, const char *,
                             const void *, size_t, const char *);
@@ -243,6 +308,7 @@ static krb5_error_code bad_404(struct bx509_request_desc *, const char *);
 static krb5_error_code bad_405(struct bx509_request_desc *, const char *);
 static krb5_error_code bad_500(struct bx509_request_desc *, krb5_error_code, const char *);
 static krb5_error_code bad_503(struct bx509_request_desc *, krb5_error_code, const char *);
+static heim_mhd_result validate_csrf_token(struct bx509_request_desc *r);
 
 static int
 validate_token(struct bx509_request_desc *r)
@@ -409,16 +475,20 @@ mk_pkix_store(char **pkix_store)
     int ret = ENOMEM;
     int fd;
 
+    if (*pkix_store) {
+        const char *fn = strchr(*pkix_store, ':');
+
+        fn = fn ? fn + 1 : *pkix_store;
+        (void) unlink(fn);
+    }
+
+    free(*pkix_store);
     *pkix_store = NULL;
     if (asprintf(&s, "PEM-FILE:%s/pkix-XXXXXX", cache_dir) == -1 ||
         s == NULL) {
         free(s);
         return ret;
     }
-    /*
-     * This way of using mkstemp() isn't safer than mktemp(), but we want to
-     * quiet the warning that we'd get if we used mktemp().
-     */
     if ((fd = mkstemp(s + sizeof("PEM-FILE:") - 1)) == -1) {
         free(s);
         return errno;
@@ -428,11 +498,6 @@ mk_pkix_store(char **pkix_store)
     return 0;
 }
 
-/*
- * XXX Shouldn't be a body, but a status message.  The body should be
- * configurable to be from a file.  MHD doesn't give us a way to set the
- * response status message though, just the body.
- */
 static krb5_error_code
 resp(struct bx509_request_desc *r,
      int http_status_code,
@@ -442,26 +507,31 @@ resp(struct bx509_request_desc *r,
      size_t bodylen,
      const char *token)
 {
-    struct MHD_Response *response;
     int mret = MHD_YES;
 
+    if (r->response)
+        return MHD_YES;
+
     (void) gettimeofday(&r->tv_end, NULL);
     if (http_status_code == MHD_HTTP_OK ||
         http_status_code == MHD_HTTP_TEMPORARY_REDIRECT)
         audit_trail(r, 0);
 
-    response = MHD_create_response_from_buffer(bodylen, rk_UNCONST(body),
-                                               rmmode);
-    if (response == NULL)
+    r->response = MHD_create_response_from_buffer(bodylen, rk_UNCONST(body),
+                                                  rmmode);
+    if (r->response == NULL)
         return -1;
-    mret = MHD_add_response_header(response, MHD_HTTP_HEADER_CACHE_CONTROL,
-                                   "no-store, max-age=0");
+    if (r->csrf_token)
+        mret = MHD_add_response_header(r->response, "X-CSRF-Token", r->csrf_token);
+    if (mret == MHD_YES)
+        mret = MHD_add_response_header(r->response, MHD_HTTP_HEADER_CACHE_CONTROL,
+                                       "no-store, max-age=0");
     if (mret == MHD_YES && http_status_code == MHD_HTTP_UNAUTHORIZED) {
-        mret = MHD_add_response_header(response,
+        mret = MHD_add_response_header(r->response,
                                        MHD_HTTP_HEADER_WWW_AUTHENTICATE,
                                        "Bearer");
         if (mret == MHD_YES)
-            mret = MHD_add_response_header(response,
+            mret = MHD_add_response_header(r->response,
                                            MHD_HTTP_HEADER_WWW_AUTHENTICATE,
                                            "Negotiate");
     } else if (mret == MHD_YES && http_status_code == MHD_HTTP_TEMPORARY_REDIRECT) {
@@ -470,21 +540,21 @@ resp(struct bx509_request_desc *r,
         /* XXX Move this */
         redir = MHD_lookup_connection_value(r->connection, MHD_GET_ARGUMENT_KIND,
                                             "redirect");
-        mret = MHD_add_response_header(response, MHD_HTTP_HEADER_LOCATION,
+        mret = MHD_add_response_header(r->response, MHD_HTTP_HEADER_LOCATION,
                                        redir);
         if (mret != MHD_NO && token)
-            mret = MHD_add_response_header(response,
+            mret = MHD_add_response_header(r->response,
                                            MHD_HTTP_HEADER_AUTHORIZATION,
                                            token);
     }
     if (mret == MHD_YES && content_type) {
-        mret = MHD_add_response_header(response,
+        mret = MHD_add_response_header(r->response,
                                        MHD_HTTP_HEADER_CONTENT_TYPE,
                                        content_type);
     }
     if (mret == MHD_YES)
-        mret = MHD_queue_response(r->connection, http_status_code, response);
-    MHD_destroy_response(response);
+        mret = MHD_queue_response(r->connection, http_status_code, r->response);
+    MHD_destroy_response(r->response);
     return mret == MHD_NO ? -1 : 0;
 }
 
@@ -520,7 +590,7 @@ bad_reqv(struct bx509_request_desc *r,
             emsg = strerror(code);
     }
 
-    ret = vasprintf(&formatted, fmt, ap) == -1;
+    ret = vasprintf(&formatted, fmt, ap);
     if (code) {
         if (ret > -1 && formatted)
             ret = asprintf(&msg, "%s: %s (%d)", formatted, emsg, (int)code);
@@ -601,6 +671,13 @@ bad_405(struct bx509_request_desc *r, const char *method)
                    "Method not supported: %s", method);
 }
 
+static krb5_error_code
+bad_413(struct bx509_request_desc *r)
+{
+    return bad_req(r, E2BIG, MHD_HTTP_METHOD_NOT_ALLOWED,
+                   "POST request body too large");
+}
+
 static krb5_error_code
 bad_500(struct bx509_request_desc *r,
         krb5_error_code ret,
@@ -871,20 +948,28 @@ addr_to_string(krb5_context context,
         snprintf(str, len, "<family=%d>", addr->sa_family);
 }
 
+static void clean_req_desc(struct bx509_request_desc *);
+
 static krb5_error_code
 set_req_desc(struct MHD_Connection *connection,
+             const char *method,
              const char *url,
-             struct bx509_request_desc *r)
+             struct bx509_request_desc **rp)
 {
+    struct bx509_request_desc *r;
     const union MHD_ConnectionInfo *ci;
     const char *token;
     krb5_error_code ret;
 
-    memset(r, 0, sizeof(*r));
+    *rp = NULL;
+    if ((r = calloc(1, sizeof(*r))) == NULL)
+        return ENOMEM;
     (void) gettimeofday(&r->tv_start, NULL);
 
     ret = get_krb5_context(&r->context);
     r->connection = connection;
+    r->response = NULL;
+    r->pp = NULL;
     r->request.data = "<HTTP-REQUEST>";
     r->request.length = sizeof("<HTTP-REQUEST>");
     r->from = r->frombuf;
@@ -893,12 +978,17 @@ set_req_desc(struct MHD_Connection *connection,
     r->hcontext = r->context ? r->context->hcontext : NULL;
     r->config = NULL;
     r->logf = logfac;
+    r->csrf_token = NULL;
+    r->free_list = NULL;
+    r->method = method;
     r->reqtype = url;
     r->target = r->redir = NULL;
     r->pkix_store = NULL;
     r->for_cname = NULL;
     r->freeme1 = NULL;
     r->reason = NULL;
+    r->tgts_filename = NULL;
+    r->tgts = NULL;
     r->ccname = NULL;
     r->reply = NULL;
     r->sname = NULL;
@@ -934,6 +1024,10 @@ set_req_desc(struct MHD_Connection *connection,
 
     }
 
+    if (ret == 0)
+        *rp = r;
+    else
+        clean_req_desc(r);
     return ret;
 }
 
@@ -942,6 +1036,13 @@ clean_req_desc(struct bx509_request_desc *r)
 {
     if (!r)
         return;
+    while (r->free_list) {
+        struct free_tend_list *ftl = r->free_list;
+        r->free_list = r->free_list->next;
+        free(ftl->freeme1);
+        free(ftl->freeme2);
+        free(ftl);
+    }
     if (r->pkix_store) {
         const char *fn = strchr(r->pkix_store, ':');
 
@@ -955,6 +1056,7 @@ clean_req_desc(struct bx509_request_desc *r)
     }
     krb5_free_addresses(r->context, &r->tgt_addresses);
     hx509_request_free(&r->req);
+    heim_release(r->attributes);
     heim_release(r->reason);
     heim_release(r->kv);
     if (r->ccname && r->cckind == K5_CREDS_EPHEMERAL) {
@@ -964,11 +1066,22 @@ clean_req_desc(struct bx509_request_desc *r)
             fn += sizeof("FILE:") - 1;
         (void) unlink(fn);
     }
+    if (r->tgts)
+        (void) fclose(r->tgts);
+    if (r->tgts_filename) {
+        (void) unlink(r->tgts_filename);
+        free(r->tgts_filename);
+    }
+    /* No need to destroy r->response */
+    if (r->pp)
+        MHD_destroy_post_processor(r->pp);
+    free(r->csrf_token);
     free(r->pkix_store);
     free(r->freeme1);
     free(r->ccname);
     free(r->cname);
     free(r->sname);
+    free(r);
 }
 
 /* Implements GETs of /bx509 */
@@ -984,9 +1097,6 @@ bx509(struct bx509_request_desc *r)
     if (csr == NULL)
         return bad_400(r, EINVAL, "CSR is missing");
 
-    if ((ret = validate_token(r)))
-        return ret; /* validate_token() calls bad_req() */
-
     if (r->cname == NULL)
         return bad_403(r, EINVAL,
                        "Could not extract principal name from token");
@@ -1424,9 +1534,8 @@ k5_get_creds(struct bx509_request_desc *r, enum k5_creds_kind kind)
     if ((ret = k5_do_CA(r)))
         return ret; /* k5_do_CA() calls bad_req() */
 
-    if (ret == 0 && (ret = do_pkinit(r, kind)))
-        ret = bad_403(r, ret,
-                      "Could not acquire Kerberos credentials using PKINIT");
+    if (ret == 0)
+        ret = do_pkinit(r, kind);
     return ret;
 }
 
@@ -1682,15 +1791,11 @@ bnegotiate(struct bx509_request_desc *r)
     char *nego_tok = NULL;
 
     ret = bnegotiate_get_target(r);
-    if (ret == 0) {
-        heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS, "target", "%s",
-                         r->target ? r->target : "<unknown>");
-        heim_audit_setkv_bool((heim_svc_req_desc)r, "redir", !!r->redir);
-        ret = validate_token(r);
-    }
-    /* bnegotiate_get_target() and validate_token() call bad_req() */
     if (ret)
-        return ret;
+        return ret; /* bnegotiate_get_target() calls bad_req() */
+    heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS, "target", "%s",
+                     r->target ? r->target : "<unknown>");
+    heim_audit_setkv_bool((heim_svc_req_desc)r, "redir", !!r->redir);
 
     /*
      * Make sure we have Kerberos credentials for cprinc.  If we have them
@@ -1702,7 +1807,8 @@ bnegotiate(struct bx509_request_desc *r)
      */
     ret = k5_get_creds(r, K5_CREDS_CACHED);
     if (ret)
-        return ret;
+        return bad_403(r, ret,
+                      "Could not acquire Kerberos credentials using PKINIT");
 
     /* Acquire the Negotiate token and output it */
     if (ret == 0 && r->ccname != NULL)
@@ -1795,10 +1901,17 @@ get_tgt_param_cb(void *d,
 /*
  * Implements /get-tgt end-point.
  *
- * Query parameters (mutually exclusive):
+ * Query parameters:
  *
  *  - cname=<name> (client principal name, if not the same as the authenticated
- *                  name, then this will be impersonated if allowed)
+ *                  name, then this will be impersonated if allowed; may be
+ *                  given only once)
+ *
+ *  - address=<IP> (IP address to add as a ticket address; may be given
+ *                  multiple times)
+ *
+ *  - lifetime=<time> (requested lifetime for the ticket; may be given only
+ *                     once)
  */
 static krb5_error_code
 get_tgt(struct bx509_request_desc *r)
@@ -1812,12 +1925,9 @@ get_tgt(struct bx509_request_desc *r)
                                                MHD_GET_ARGUMENT_KIND, "cname");
     if (r->for_cname && r->for_cname[0] == '\0')
         r->for_cname = NULL;
-    ret = validate_token(r);
-    if (ret == 0)
-        ret = authorize_TGT_REQ(r);
-    /* validate_token() and authorize_TGT_REQ() call bad_req() */
+    ret = authorize_TGT_REQ(r);
     if (ret)
-        return ret;
+        return ret; /* authorize_TGT_REQ() calls bad_req() */
 
     r->error_code = 0;
     (void) MHD_get_connection_values(r->connection, MHD_GET_ARGUMENT_KIND,
@@ -1828,7 +1938,8 @@ get_tgt(struct bx509_request_desc *r)
     if (ret == 0)
         ret = k5_get_creds(r, K5_CREDS_EPHEMERAL);
     if (ret)
-        return ret;
+        return bad_403(r, ret,
+                      "Could not acquire Kerberos credentials using PKINIT");
 
     fn = strchr(r->ccname, ':');
     if (fn == NULL)
@@ -1843,6 +1954,262 @@ get_tgt(struct bx509_request_desc *r)
     return ret;
 }
 
+static int
+get_tgts_accumulate_ccache_write_json(struct bx509_request_desc *r,
+                                      krb5_error_code code,
+                                      const char *data,
+                                      size_t datalen)
+{
+    heim_object_t k, v;
+    heim_string_t text;
+    heim_error_t e = NULL;
+    heim_dict_t o;
+    int ret;
+
+    o = heim_dict_create(9);
+    k = heim_string_create("name");
+    v = heim_string_create(r->for_cname);
+    if (o && k && v)
+        ret = heim_dict_set_value(o, k, v);
+    else
+        ret = errno;
+
+    if (ret == 0) {
+        heim_release(v);
+        heim_release(k);
+        k = heim_string_create("error_code");
+        v = heim_number_create(code);
+        if (k && v)
+            ret = heim_dict_set_value(o, k, v);
+    }
+    if (ret == 0 && data != NULL) {
+        heim_release(v);
+        heim_release(k);
+        k = heim_string_create("ccache");
+        v = heim_data_create(data, datalen);
+        if (k && v)
+            ret = heim_dict_set_value(o, k, v);
+    }
+    if (ret == 0 && code != 0) {
+        heim_release(v);
+        heim_release(k);
+        k = heim_string_create("error");
+        v = heim_string_create(krb5_get_error_message(r->context, code));
+        if (k && v)
+            ret = heim_dict_set_value(o, k, v);
+    }
+    heim_release(v);
+    heim_release(k);
+    if (ret) {
+        heim_release(o);
+        return bad_503(r, errno, "Out of memory");
+    }
+
+    text = heim_json_copy_serialize(o,
+                                    HEIM_JSON_F_NO_DATA_DICT |
+                                    HEIM_JSON_F_ONE_LINE,
+                                    &e);
+    if (text) {
+        const char *s = heim_string_get_utf8(text);
+
+        (void) fwrite(s, strlen(s), 1, r->tgts);
+    } else {
+        const char *s = NULL;
+        v = heim_error_copy_string(e);
+        if (v)
+            s = heim_string_get_utf8(v);
+        if (s == NULL)
+            s = "<unknown encoder error>";
+        krb5_log_msg(r->context, logfac, 1, NULL, "Failed to encode JSON text with ccache or error for %s: %s",
+                     r->for_cname, s);
+        heim_release(v);
+    }
+    heim_release(text);
+    heim_release(o);
+    return MHD_YES;
+}
+
+/* Writes one ccache to a response file, as JSON */
+static int
+get_tgts_accumulate_ccache(struct bx509_request_desc *r, krb5_error_code ret)
+{
+    const char *fn;
+    size_t bodylen = 0;
+    void *body = NULL;
+    int res;
+
+    if (r->tgts == NULL) {
+        int fd = -1;
+
+        if (asprintf(&r->tgts_filename,
+                     "%s/tgts-json-XXXXXX", cache_dir) == -1 ||
+            r->tgts_filename == NULL) {
+            free(r->tgts_filename);
+            r->tgts_filename = NULL;
+
+            return bad_enomem(r, r->error_code = ENOMEM);
+        }
+        if ((fd = mkstemp(r->tgts_filename)) == -1)
+            return bad_req(r, errno, MHD_HTTP_SERVICE_UNAVAILABLE,
+                           "%s", strerror(r->error_code = errno));
+        if ((r->tgts = fdopen(fd, "w+")) == NULL) {
+            (void) close(fd);
+            return bad_req(r, errno, MHD_HTTP_SERVICE_UNAVAILABLE,
+                           "%s", strerror(r->error_code = errno));
+        }
+    }
+
+    if (ret == 0) {
+        fn = strchr(r->ccname, ':');
+        if (fn == NULL)
+            return bad_req(r, errno, MHD_HTTP_SERVICE_UNAVAILABLE,
+                           "Internal error (invalid credentials cache name)");
+        fn++;
+        if ((r->error_code = rk_undumpdata(fn, &body, &bodylen)))
+            return bad_req(r, errno, MHD_HTTP_SERVICE_UNAVAILABLE,
+                           "%s", strerror(r->error_code));
+        (void) unlink(fn);
+        free(r->ccname);
+        r->ccname = NULL;
+        if (bodylen > INT_MAX >> 4) {
+            free(body);
+            return bad_req(r, errno, MHD_HTTP_SERVICE_UNAVAILABLE,
+                           "Credentials cache too large!");
+        }
+    }
+
+    res = get_tgts_accumulate_ccache_write_json(r, ret, body, bodylen);
+    free(body);
+    return res;
+}
+
+static heim_mhd_result
+get_tgts_param_authorize_cb(void *d,
+                            enum MHD_ValueKind kind,
+                            const char *key,
+                            const char *val)
+{
+    struct bx509_request_desc *r = d;
+    krb5_error_code ret = 0;
+
+    if (strcmp(key, "cname") != 0 || val == NULL)
+        return MHD_YES;
+
+    if (r->req == NULL) {
+        ret = hx509_request_init(r->context->hx509ctx, &r->req);
+        if (ret == 0)
+            ret = hx509_request_add_eku(r->context->hx509ctx, r->req,
+                                        ASN1_OID_ID_PKEKUOID);
+        if (ret)
+            return bad_500(r, ret, "Out of resources");
+    }
+    heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
+                     "requested_krb5PrincipalName", "%s", val);
+    ret = hx509_request_add_pkinit(r->context->hx509ctx, r->req,
+                                   val);
+    if (ret)
+        return bad_403(r, ret, "Not authorized to requested TGT");
+    return MHD_YES;
+}
+
+/* For each requested principal, produce a ccache */
+static heim_mhd_result
+get_tgts_param_execute_cb(void *d,
+                          enum MHD_ValueKind kind,
+                          const char *key,
+                          const char *val)
+{
+    struct bx509_request_desc *r = d;
+    heim_mhd_result res = MHD_YES;
+    krb5_error_code ret;
+
+    if (strcmp(key, "cname") == 0 && val) {
+        /* Handled upstairs */
+        r->for_cname = val;
+        ret = k5_get_creds(r, K5_CREDS_EPHEMERAL);
+        res = get_tgts_accumulate_ccache(r, ret);
+    } else {
+        /* Handled upstairs */
+    }
+    return res;
+}
+
+/*
+ * Implements /get-tgts end-point.
+ *
+ * Query parameters:
+ *
+ *  - cname=<name> (client principal name, if not the same as the authenticated
+ *                  name, then this will be impersonated if allowed; may be
+ *                  given multiple times)
+ */
+static krb5_error_code
+get_tgts(struct bx509_request_desc *r)
+{
+    krb5_error_code ret;
+    krb5_principal p = NULL;
+    size_t bodylen;
+    void *body;
+    int res = MHD_YES;
+
+    /* Prep to authorize */
+    ret = krb5_parse_name(r->context, r->cname, &p);
+    if (ret)
+        return bad_403(r, ret, "Could not parse caller principal name");
+    if (ret == 0) {
+        /* Extract q-params other than `cname' */
+        r->error_code = 0;
+        res = MHD_get_connection_values(r->connection, MHD_GET_ARGUMENT_KIND,
+                                        get_tgt_param_cb, r);
+        if (r->response || res == MHD_NO)
+            return res;
+
+        ret = r->error_code;
+    }
+    if (ret == 0) {
+        /* Authorize requested client principal names (calls bad_req()) */
+        r->error_code = 0;
+        res = MHD_get_connection_values(r->connection, MHD_GET_ARGUMENT_KIND,
+                                        get_tgts_param_authorize_cb, r);
+        if (r->response || res == MHD_NO)
+            return res;
+
+        ret = r->error_code;
+        if (ret == 0) {
+            ret = kdc_authorize_csr(r->context, "get-tgt", r->req, p);
+            if (ret) {
+                krb5_free_principal(r->context, p);
+                return bad_403(r, ret, "Permission denied");
+            }
+        }
+        hx509_request_free(&r->req);
+    }
+    if (ret == 0) {
+        /* get_tgts_param_execute_cb() calls bad_req() */
+        r->error_code = 0;
+        res = MHD_get_connection_values(r->connection, MHD_GET_ARGUMENT_KIND,
+                                        get_tgts_param_execute_cb, r);
+        if (r->response || res == MHD_NO)
+            return res;
+        ret = r->error_code;
+    }
+    krb5_free_principal(r->context, p);
+
+    /*
+     * get_tgts_param_execute_cb() will write its JSON response to the file
+     * named by r->ccname.
+     */
+    if (fflush(r->tgts) != 0)
+        return bad_503(r, ret, "Could not get TGT");
+    if ((errno = rk_undumpdata(r->tgts_filename, &body, &bodylen)))
+        return bad_503(r, ret, "Could not get TGT");
+
+    ret = resp(r, MHD_HTTP_OK, MHD_RESPMEM_MUST_COPY,
+               "application/x-krb5-ccaches-json", body, bodylen, NULL);
+    free(body);
+    return ret;
+}
+
 static krb5_error_code
 health(const char *method, struct bx509_request_desc *r)
 {
@@ -1856,7 +2223,250 @@ health(const char *method, struct bx509_request_desc *r)
 
 }
 
-/* Implements the entirety of this REST service */
+static krb5_error_code
+mac_csrf_token(struct bx509_request_desc *r, krb5_storage *sp)
+{
+    krb5_error_code ret;
+    krb5_data data;
+    char mac[EVP_MAX_MD_SIZE];
+    unsigned int maclen = sizeof(mac);
+    HMAC_CTX *ctx = NULL;
+
+    ret = krb5_storage_to_data(sp, &data);
+    if (ret == 0 && (ctx = HMAC_CTX_new()) == NULL)
+            ret = krb5_enomem(r->context);
+    /* HMAC the token body and the client principal name */
+    if (ret == 0) {
+        if (HMAC_Init_ex(ctx, csrf_key, sizeof(csrf_key),
+                         EVP_sha256(),
+                         NULL) == 0) {
+            HMAC_CTX_cleanup(ctx);
+            ret = krb5_enomem(r->context);
+        } else {
+            HMAC_Update(ctx, data.data, data.length);
+            if (r->cname)
+                HMAC_Update(ctx, r->cname, strlen(r->cname));
+            HMAC_Final(ctx, mac, &maclen);
+            HMAC_CTX_cleanup(ctx);
+            krb5_data_free(&data);
+            data.length = maclen;
+            data.data = mac;
+            if (krb5_storage_write(sp, mac, maclen) != maclen)
+                ret = krb5_enomem(r->context);
+        }
+    }
+    if (ctx)
+        HMAC_CTX_free(ctx);
+    return ret;
+}
+
+/*
+ * Make a CSRF token.  If one is also given, make one with the same body
+ * content so we can check the HMAC.
+ *
+ * Outputs the token and its age.  Do not use either if the token does not
+ * equal the given token.
+ */
+static krb5_error_code
+make_csrf_token(struct bx509_request_desc *r,
+                const char *given,
+                char **token,
+                int64_t *age)
+{
+    krb5_error_code ret = 0;
+    unsigned char given_decoded[128];
+    krb5_storage *sp = NULL;
+    krb5_data data;
+    ssize_t dlen = -1;
+    uint64_t nonce;
+    int64_t t = 0;
+
+
+    *age = 0;
+    data.data = NULL;
+    data.length = 0;
+    if (given) {
+        size_t len = strlen(given);
+
+        /* Extract issue time and nonce from token */
+        if (len >= sizeof(given_decoded))
+            ret = ERANGE;
+        if (ret == 0 && (dlen = rk_base64_decode(given, &given_decoded)) <= 0)
+            ret = errno;
+        if (ret == 0 &&
+            (sp = krb5_storage_from_mem(given_decoded, dlen)) == NULL)
+            ret = krb5_enomem(r->context);
+        if (ret == 0)
+            ret = krb5_ret_int64(sp, &t);
+        if (ret == 0)
+            ret = krb5_ret_uint64(sp, &nonce);
+        krb5_storage_free(sp);
+        sp = NULL;
+        if (ret == 0)
+            *age = time(NULL) - t;
+    } else {
+        t = time(NULL);
+        krb5_generate_random_block((void *)&nonce, sizeof(nonce));
+    }
+
+    if (ret == 0 && (sp = krb5_storage_emem()) == NULL)
+        ret = krb5_enomem(r->context);
+    if (ret == 0)
+        ret = krb5_store_int64(sp, t);
+    if (ret == 0)
+        ret = krb5_store_uint64(sp, nonce);
+    if (ret == 0)
+        ret = mac_csrf_token(r, sp);
+    if (ret == 0)
+        ret = krb5_storage_to_data(sp, &data);
+    if (ret == 0 && data.length > INT_MAX)
+        ret = ERANGE;
+    if (ret == 0 &&
+        (dlen = rk_base64_encode(data.data, data.length, token)) < 0)
+        ret = errno;
+    krb5_storage_free(sp);
+    krb5_data_free(&data);
+    return ret;
+}
+
+static heim_mhd_result
+validate_csrf_token(struct bx509_request_desc *r)
+{
+    const char *given;
+    int64_t age;
+    krb5_error_code ret;
+
+    if ((((csrf_prot_type & CSRF_PROT_GET_WITH_HEADER) &&
+          strcmp(r->method, "GET") == 0) ||
+         ((csrf_prot_type & CSRF_PROT_POST_WITH_HEADER) &&
+          strcmp(r->method, "POST") == 0)) &&
+        MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
+                                    csrf_header) == NULL) {
+        ret = bad_req(r, EACCES, MHD_HTTP_FORBIDDEN,
+                      "Request must have header \"%s\"", csrf_header);
+        return ret == -1 ? MHD_NO : MHD_YES;
+    }
+
+    if (strcmp(r->method, "GET") == 0 &&
+        !(csrf_prot_type & CSRF_PROT_GET_WITH_TOKEN))
+        return 0;
+    if (strcmp(r->method, "POST") == 0 &&
+        !(csrf_prot_type & CSRF_PROT_POST_WITH_TOKEN))
+        return 0;
+
+    given = MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
+                                        "X-CSRF-Token");
+    ret = make_csrf_token(r, given, &r->csrf_token, &age);
+    if (ret)
+        return bad_503(r, ret, "Could not make or validate CSRF token");
+    if (given == NULL)
+        return bad_req(r, EACCES, MHD_HTTP_FORBIDDEN,
+                       "CSRF token needed; copy the X-CSRF-Token: response "
+                       "header to your next POST");
+    if (strlen(given) != strlen(r->csrf_token) ||
+        strcmp(given, r->csrf_token) != 0)
+        return bad_403(r, EACCES, "Invalid CSRF token");
+    if (age > 300)
+        return bad_403(r, EACCES, "CSRF token expired");
+    return 0;
+}
+
+/*
+ * MHD callback to free the request context when MHD is done sending the
+ * response.
+ */
+static void
+cleanup_req(void *cls,
+            struct MHD_Connection *connection,
+            void **con_cls,
+            enum MHD_RequestTerminationCode toe)
+{
+    struct bx509_request_desc *r = *con_cls;
+
+    (void)cls;
+    (void)connection;
+    (void)toe;
+    clean_req_desc(r);
+    *con_cls = NULL;
+}
+
+/* Callback for MHD POST form data processing */
+static heim_mhd_result
+ip(void *cls,
+   enum MHD_ValueKind kind,
+   const char *key,
+   const char *content_name,
+   const char *content_type,
+   const char *transfer_encoding,
+   const char *val,
+   uint64_t off,
+   size_t size)
+{
+    struct bx509_request_desc *r = cls;
+    struct free_tend_list *ftl = calloc(1, sizeof(*ftl));
+    char *keydup = strdup(key);
+    char *valdup = strndup(val, size);
+
+    (void)content_name;         /* MIME attachment name */
+    (void)content_type;         /* Don't care -- MHD liked it */
+    (void)transfer_encoding;
+    (void)off;                  /* Offset in POST data */
+
+    /*
+     * We're going to MHD_set_connection_value(), but we need copies because
+     * the MHD POST processor quite naturally keeps none of the chunks
+     * received.
+     */
+    if (ftl == NULL || keydup == NULL || valdup == NULL) {
+        free(ftl);
+        free(keydup);
+        return MHD_NO;
+    }
+    ftl->freeme1 = keydup;
+    ftl->freeme2 = valdup;
+    ftl->next = r->free_list;
+    r->free_list = ftl;
+
+    return MHD_set_connection_value(r->connection, MHD_GET_ARGUMENT_KIND,
+                                    keydup, valdup);
+}
+
+typedef krb5_error_code (*handler)(struct bx509_request_desc *);
+
+struct route {
+    const char *local_part;
+    handler h;
+    unsigned int referer_ok:1;
+} routes[] = {
+    { "/get-cert", bx509, 0 },
+    { "/get-negotiate-token", bnegotiate, 1 },
+    { "/get-tgt", get_tgt, 0 },
+    { "/get-tgts", get_tgts, 0 },
+    /* Lousy old names to be removed eventually */
+    { "/bnegotiate", bnegotiate, 1 },
+    { "/bx509", bx509, 0 },
+};
+
+/*
+ * We should commonalize all of:
+ *
+ *  - route() and related infrastructure
+ *  - including the CSRF functions
+ *  - and Negotiate/Bearer authentication
+ *
+ * so that we end up with a simple framework that our daemons can invoke to
+ * serve simple functions that take a fully-consumed request and send a
+ * response.
+ *
+ * Then:
+ *
+ *  - split out the CA and non-CA bits into separate daemons using that common
+ *    code,
+ *  - make httpkadmind use that common code,
+ *  - abstract out all the MHD stuff.
+ */
+
+/* Routes requests */
 static heim_mhd_result
 route(void *cls,
       struct MHD_Connection *connection,
@@ -1867,46 +2477,137 @@ route(void *cls,
       size_t *upload_data_size,
       void **ctx)
 {
-    static int aptr = 0;
-    struct bx509_request_desc r;
+    struct bx509_request_desc *r = *ctx;
+    size_t i;
     int ret;
 
-    if (*ctx == NULL) {
+    if (r == NULL) {
         /*
          * This is the first call, right after headers were read.
          *
          * We must return quickly so that any 100-Continue might be sent with
-         * celerity.
+         * celerity.  We want to make sure to send any 401s early, so we check
+         * WWW-Authenticate now, not later.
          *
-         * We'll get called again to really do the processing.  If we handled
-         * POSTs then we'd also get called with upload_data != NULL between the
-         * first and last calls.  We need to keep no state between the first
-         * and last calls, but we do need to distinguish first and last call,
-         * so we use the ctx argument for this.
+         * We'll get called again to really do the processing.  If we're
+         * handling a POST then we'll also get called with upload_data != NULL,
+         * possibly multiple times.
          */
-        *ctx = &aptr;
-        return MHD_YES;
+        if ((ret = set_req_desc(connection, method, url, &r)))
+            return bad_503(r, ret, "Could not initialize request state");
+        *ctx = r;
+
+        /* All requests other than /health require authentication */
+        if (strcmp(url, "/health") == 0)
+            return MHD_YES;
+
+        /*
+         * Authenticate and do CSRF protection.
+         *
+         * If the Referer: header is set in the request, we don't want CSRF
+         * protection as only /get-negotiate-token will accept a Referer:
+         * header (see routes[] and below), so we'll call validate_csrf_token()
+         * for the other routes or reject the request for having Referer: set.
+         */
+        ret = validate_token(r);
+        if (ret == 0 &&
+            MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND, "Referer") == NULL)
+            ret = validate_csrf_token(r);
+
+        /*
+         * As this is the initial call to this handler, we must return now.
+         *
+         * If authentication or CSRF protection failed then we'll already have
+         * enqueued a 401, 403, or 5xx response and then we're done.
+         *
+         * If both authentication and CSRF protection succeeded then no
+         * response has been queued up and we'll get called again to finally
+         * process the request, then this entire if block will not be executed.
+         */
+        return ret == -1 ? MHD_NO : MHD_YES;
+    }
+
+    /* Validate HTTP method */
+    if (strcmp(method, "GET") != 0 &&
+        strcmp(method, "POST") != 0 &&
+        strcmp(method, "HEAD") != 0) {
+        return bad_405(r, method) == -1 ? MHD_NO : MHD_YES;
     }
 
-    if ((ret = set_req_desc(connection, url, &r)))
-        return bad_503(&r, ret, "Could not initialize request state");
     if ((strcmp(method, "HEAD") == 0 || strcmp(method, "GET") == 0) &&
-        (strcmp(url, "/health") == 0 || strcmp(url, "/") == 0))
-        ret = health(method, &r);
-    else if (strcmp(method, "GET") != 0)
-        ret = bad_405(&r, method);
-    else if (strcmp(url, "/get-cert") == 0 ||
-             strcmp(url, "/bx509") == 0) /* old name */
-        ret = bx509(&r);
-    else if (strcmp(url, "/get-negotiate-token") == 0 ||
-             strcmp(url, "/bnegotiate") == 0) /* old name */
-        ret = bnegotiate(&r);
-    else if (strcmp(url, "/get-tgt") == 0)
-        ret = get_tgt(&r);
-    else
-        ret = bad_404(&r, url);
+        (strcmp(url, "/health") == 0 || strcmp(url, "/") == 0)) {
+        /* /health end-point -- no authentication, no CSRF, no nothing */
+        return health(method, r) == -1 ? MHD_NO : MHD_YES;
+    }
+
+    if (r->cname == NULL)
+        return bad_401(r, "Authorization token is missing");
 
-    clean_req_desc(&r);
+    if (strcmp(method, "POST") == 0 && *upload_data_size != 0) {
+        /*
+         * Consume all the POST body and set form data as MHD_GET_ARGUMENT_KIND
+         * (as if they had been URI query parameters).
+         *
+         * We have to do this before we can MHD_queue_response() as MHD will
+         * not consume the rest of the request body on its own, so it's an
+         * error to MHD_queue_response() before we've done this, and if we do
+         * then MHD just closes the connection.
+         *
+         * 4KB should be more than enough buffer space for all the keys we
+         * expect.
+         */
+        if (r->pp == NULL)
+            r->pp = MHD_create_post_processor(connection, 4096, ip, r);
+        if (r->pp == NULL) {
+            ret = bad_503(r, errno ? errno : ENOMEM,
+                          "Could not consume POST data");
+            return ret == -1 ? MHD_NO : MHD_YES;
+        }
+        if (r->post_data_size + *upload_data_size > 1UL<<17) {
+            return bad_413(r) == -1 ? MHD_NO : MHD_YES;
+        }
+        r->post_data_size += *upload_data_size;
+        if (MHD_post_process(r->pp, upload_data,
+                             *upload_data_size) == MHD_NO) {
+            ret = bad_503(r, errno ? errno : ENOMEM,
+                          "Could not consume POST data");
+            return ret == -1 ? MHD_NO : MHD_YES;
+        }
+        *upload_data_size = 0;
+        return MHD_YES;
+    }
+
+    /*
+     * Either this is a HEAD, a GET, or a POST whose request body has now been
+     * received completely and processed.
+     */
+
+    /* Allow GET? */
+    if (strcmp(method, "GET") == 0 && !allow_GET_flag) {
+        /* No */
+        return bad_405(r, method) == -1 ? MHD_NO : MHD_YES;
+    }
+
+    for (i = 0; i < sizeof(routes)/sizeof(routes[0]); i++) {
+        if (strcmp(url, routes[i].local_part) != 0)
+            continue;
+        if (!routes[i].referer_ok &&
+            MHD_lookup_connection_value(r->connection,
+                                        MHD_HEADER_KIND,
+                                        "Referer") != NULL) {
+            ret = bad_req(r, EACCES, MHD_HTTP_FORBIDDEN,
+                          "GET from browser not allowed");
+            return ret == -1 ? MHD_NO : MHD_YES;
+        }
+        if (strcmp(method, "HEAD") == 0)
+            ret = resp(r, MHD_HTTP_OK, MHD_RESPMEM_PERSISTENT, NULL, "", 0,
+                       NULL);
+        else
+            ret = routes[i].h(r);
+        return ret == -1 ? MHD_NO : MHD_YES;
+    }
+
+    ret = bad_404(r, url);
     return ret == -1 ? MHD_NO : MHD_YES;
 }
 
@@ -1914,14 +2615,21 @@ static struct getargs args[] = {
     { "help", 'h', arg_flag, &help_flag, "Print usage message", NULL },
     { "version", '\0', arg_flag, &version_flag, "Print version", NULL },
     { NULL, 'H', arg_strings, &audiences,
-        "expected token audience(s) of bx509 service", "HOSTNAME" },
+        "expected token audience(s)", "HOSTNAME" },
     { "daemon", 'd', arg_flag, &daemonize, "daemonize", "daemonize" },
     { "daemon-child", 0, arg_flag, &daemon_child_fd, NULL, NULL }, /* priv */
     { "reverse-proxied", 0, arg_flag, &reverse_proxied_flag,
         "reverse proxied", "listen on 127.0.0.1 and do not use TLS" },
-    { NULL, 'p', arg_integer, &port, "PORT", "port number (default: 443)" },
+    { "port", 'p', arg_integer, &port, "port number (default: 443)", "PORT" },
     { "cache-dir", 0, arg_string, &cache_dir,
         "cache directory", "DIRECTORY" },
+    { "allow-GET", 0, arg_negative_flag, &allow_GET_flag, NULL, NULL },
+    { "csrf-header", 0, arg_flag,
+        &csrf_header, "required request header", "HEADER-NAME" },
+    { "csrf-protection-type", 0, arg_strings, &csrf_prot_type_strs,
+        "Anti-CSRF protection type", "TYPE" },
+    { "csrf-key-file", 0, arg_string, &csrf_key_file,
+        "CSRF MAC key", "FILE" },
     { "cert", 0, arg_string, &cert_file,
         "certificate file path (PEM)", "HX509-STORE" },
     { "private-key", 0, arg_string, &priv_key_file,
@@ -1935,9 +2643,10 @@ static int
 usage(int e)
 {
     arg_printusage(args, sizeof(args) / sizeof(args[0]), "bx509",
-        "\nServes RESTful GETs of /bx509 and /bnegotiate,\n"
-        "performing corresponding kx509 and, possibly, PKINIT requests\n"
-        "to the KDCs of the requested realms (or just the given REALM).\n");
+        "\nServes RESTful GETs of /get-cert, /get-tgt, /get-tgts, and\n"
+        "/get-negotiate-toke, performing corresponding kx509 and, \n"
+        "possibly, PKINIT requests to the KDCs of the requested \n"
+        "realms (or just the given REALM).\n");
     exit(e);
 }
 
@@ -2009,6 +2718,67 @@ load_plugins(krb5_context context)
 #endif
 }
 
+static void
+get_csrf_prot_type(krb5_context context)
+{
+    char * const *strs = csrf_prot_type_strs.strings;
+    size_t n = csrf_prot_type_strs.num_strings;
+    size_t i;
+    char **freeme = NULL;
+
+    if (csrf_header == NULL)
+        csrf_header = krb5_config_get_string(context, NULL, "bx509d",
+                                             "csrf_protection_csrf_header",
+                                             NULL);
+
+    if (n == 0) {
+        char * const *p;
+
+        strs = freeme = krb5_config_get_strings(context, NULL, "bx509d",
+                                                "csrf_protection_type", NULL);
+        for (p = strs; p && p; p++)
+            n++;
+    }
+
+    for (i = 0; i < n; i++) {
+        if (strcmp(strs[i], "GET-with-header") == 0)
+            csrf_prot_type |= CSRF_PROT_GET_WITH_HEADER;
+        else if (strcmp(strs[i], "GET-with-token") == 0)
+            csrf_prot_type |= CSRF_PROT_GET_WITH_TOKEN;
+        else if (strcmp(strs[i], "POST-with-header") == 0)
+            csrf_prot_type |= CSRF_PROT_POST_WITH_HEADER;
+        else if (strcmp(strs[i], "POST-with-token") == 0)
+            csrf_prot_type |= CSRF_PROT_POST_WITH_TOKEN;
+    }
+    free(freeme);
+
+    /*
+     * For GETs we default to no CSRF protection as our GETable resources are
+     * safe and idempotent and we count on the browser not to make the
+     * responses available to cross-site requests.
+     *
+     * But, really, we don't want browsers even making these requests since, if
+     * the browsers behave correctly, then there's no point, and if they don't
+     * behave correctly then that could be catastrophic.  Of course, there's no
+     * guarantee that a browser won't have other catastrophic bugs, but still,
+     * we should probably change this default in the future:
+     *
+     *  if (!(csrf_prot_type & CSRF_PROT_GET_WITH_HEADER) &&
+     *      !(csrf_prot_type & CSRF_PROT_GET_WITH_TOKEN))
+     *      csrf_prot_type |= <whatever-the-new-default-should-be>;
+     */
+
+    /*
+     * For POSTs we default to CSRF protection with anti-CSRF tokens even
+     * though out POSTable resources are safe and idempotent when POSTed and we
+     * could count on the browser not to make the responses available to
+     * cross-site requests.
+     */
+    if (!(csrf_prot_type & CSRF_PROT_POST_WITH_HEADER) &&
+        !(csrf_prot_type & CSRF_PROT_POST_WITH_TOKEN))
+        csrf_prot_type |= CSRF_PROT_POST_WITH_TOKEN;
+}
+
 int
 main(int argc, char **argv)
 {
@@ -2039,6 +2809,39 @@ main(int argc, char **argv)
     if (port < 0)
         errx(1, "Port number must be given");
 
+    if ((errno = pthread_key_create(&k5ctx, k5_free_context)))
+        err(1, "Could not create thread-specific storage");
+
+    if ((errno = get_krb5_context(&context)))
+        err(1, "Could not init krb5 context");
+
+    bx509_openlog(context, "bx509d", &logfac);
+    load_plugins(context);
+
+    if (allow_GET_flag == -1)
+        warnx("It is safer to use --no-allow-GET");
+
+    get_csrf_prot_type(context);
+
+    krb5_generate_random_block((void *)&csrf_key, sizeof(csrf_key));
+    if (csrf_key_file == NULL)
+        csrf_key_file = krb5_config_get_string(context, NULL, "bx509d",
+                                               "csrf_key_file", NULL);
+    if (csrf_key_file) {
+        ssize_t bytes;
+        int fd;
+
+        fd = open(csrf_key_file, O_RDONLY);
+        if (fd == -1)
+            err(1, "CSRF key file missing %s", csrf_key_file);
+        bytes = read(fd, csrf_key, sizeof(csrf_key));
+        if (bytes == -1)
+            err(1, "Could not read CSRF key file %s", csrf_key_file);
+        if (bytes != sizeof(csrf_key))
+            errx(1, "CSRF key file too small (should be %lu) %s",
+                 (unsigned long)sizeof(csrf_key), csrf_key_file);
+    }
+
     if (audiences.num_strings == 0) {
         char localhost[MAXHOSTNAMELEN];
 
@@ -2062,15 +2865,6 @@ main(int argc, char **argv)
     if (argc != 0)
         usage(1);
 
-    if ((errno = pthread_key_create(&k5ctx, k5_free_context)))
-        err(1, "Could not create thread-specific storage");
-
-    if ((errno = get_krb5_context(&context)))
-        err(1, "Could not init krb5 context");
-
-    bx509_openlog(context, "bx509d", &logfac);
-    load_plugins(context);
-
     if (cache_dir == NULL) {
         char *s = NULL;
 
@@ -2191,16 +2985,23 @@ again:
         sin.sin_family = AF_INET;
         sin.sin_port = htons(port);
         current = MHD_start_daemon(flags, port,
+                                   /*
+                                    * This is a connection access callback.  We
+                                    * don't use it.
+                                    */
                                    NULL, NULL,
+                                   /* This is our request handler */
                                    route, (char *)NULL,
                                    MHD_OPTION_SOCK_ADDR, &sin,
                                    MHD_OPTION_CONNECTION_LIMIT, (unsigned int)200,
                                    MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int)10,
+                                   /* This is our request cleanup handler */
+                                   MHD_OPTION_NOTIFY_COMPLETED, cleanup_req, NULL,
                                    MHD_OPTION_END);
     } else if (sock != MHD_INVALID_SOCKET) {
         /*
-         * Certificate/key rollover: reuse the listen socket returned by
-         * MHD_quiesce_daemon().
+         * Restart following a possible certificate/key rollover, reusing the
+         * listen socket returned by MHD_quiesce_daemon().
          */
         current = MHD_start_daemon(flags | MHD_USE_SSL, port,
                                    NULL, NULL,
@@ -2209,10 +3010,17 @@ again:
                                    MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
                                    MHD_OPTION_CONNECTION_LIMIT, (unsigned int)200,
                                    MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int)10,
+                                   MHD_OPTION_NOTIFY_COMPLETED, cleanup_req, NULL,
                                    MHD_OPTION_LISTEN_SOCKET, sock,
                                    MHD_OPTION_END);
         sock = MHD_INVALID_SOCKET;
     } else {
+        /*
+         * Initial MHD_start_daemon(), with TLS.
+         *
+         * Subsequently we'll restart reusing the listen socket this creates.
+         * See above.
+         */
         current = MHD_start_daemon(flags | MHD_USE_SSL, port,
                                    NULL, NULL,
                                    route, (char *)NULL,
@@ -2220,6 +3028,7 @@ again:
                                    MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
                                    MHD_OPTION_CONNECTION_LIMIT, (unsigned int)200,
                                    MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int)10,
+                                   MHD_OPTION_NOTIFY_COMPLETED, cleanup_req, NULL,
                                    MHD_OPTION_END);
     }
     if (current == NULL)
index 4ea76dbe7e28588b3ee6af84f6d38aa1a027486d..275efaff501e0e03ca27c28e2ba0aaa2a8a1fc80 100644 (file)
@@ -179,7 +179,7 @@ ntlm_service(void *ctx, const heim_idata *req,
            goto failed;
 
        if (ntq.ntChallengeResponce.length != answer.length ||
-           memcmp(ntq.ntChallengeResponce.data, answer.data, answer.length) != 0) {
+           ct_memcmp(ntq.ntChallengeResponce.data, answer.data, answer.length) != 0) {
            free(answer.data);
            ret = EINVAL;
            goto failed;
index 092b4a75a4be36234d09601537aa0bc3bcaaed12..3285aaa54d0fb55a0572a7f839c0805f9733a7f9 100644 (file)
@@ -1314,7 +1314,7 @@ _kdc_do_digest(krb5_context context,
            }
 
            if (ireq.u.ntlmRequest.ntlm.length != answer.length ||
-               memcmp(ireq.u.ntlmRequest.ntlm.data, answer.data, answer.length) != 0)
+               ct_memcmp(ireq.u.ntlmRequest.ntlm.data, answer.data, answer.length) != 0)
                {
                    free(answer.data);
                    ret = EINVAL;
index 90b4f63aa692745db18790296ba89ade961ea8d1..345e9594a7d7c830c384c97ab6578be547ba6602 100644 (file)
 .Op Fl Fl daemon-child
 .Op Fl Fl reverse-proxied
 .Op Fl p Ar port number (default: 443)
+.Op Fl Fl allow-GET
+.Op Fl Fl no-allow-GET
+.Op Fl Fl GET-with-csrf-token
+.Op Fl Fl csrf-header= Ns Ar HEADER-NAME
 .Op Fl Fl temp-dir= Ns Ar DIRECTORY
 .Op Fl Fl cert=HX509-STORE
 .Op Fl Fl private-key=HX509-STORE
 .Xc
 .Oc
 .Sh DESCRIPTION
-Serves the following resources:
+Serves the following resources over HTTP:
 .Ar /get-keys and
 .Ar /get-config .
 .Pp
 The
 .Ar /get-keys
-end-point allows callers to get keytab content for named
-principals, possibly performing write operations such as creating
-a non-existent principal, or rotating its keys, if requested.
+end-point allows callers to get a principal's keys in
+.Dq keytab
+format for named principals, possibly performing write operations
+such as creating a non-existent principal, or rotating its keys,
+if requested.
+Note that this end-point can cause KDC HDB principal entries to
+be modified or created incidental to fetching the principal's
+keys.
+The use of the HTTP POST method is required when this end-point
+writes to the KDC's HDB.
+See below.
 .Pp
 The
 .Ar /get-config
@@ -96,7 +108,87 @@ end-point accepts a single query parameter:
 .Bl -tag -width Ds -offset indent
 .It Ar princ=PRINCIPAL .
 .El
+.Sh HTTP APIS
+All HTTP APIs served by this program accept POSTs, with all
+request parameters given as either URI query parameters, and/or
+as form data in the POST request body, in either
+.Ar application/x-www-form-urlencoded
+or
+.Ar multipart/formdata .
+If GETs are enabled, then request parameters must be supplied as
+URI query parameters.
+.Pp
+Note that requests that cause changes to the HDB must always be
+done via POST, never GET.
+.Pp
+URI query parameters must be of the form
+.Ar param0=value&param1=value...
+Some parameters can be given multiple values -- see the
+descriptions of the end-points.
+.Sh CROSS-SITE REQUEST FORGERY PROTECTION
+.Em None
+of the resources service by this service are intended to be
+executed by web pages.
+.Pp
+Most of the resources provided by this service are
+.Dq safe
+in the sense that they do not change server-side state besides
+logging, and in that they are idempotent, but they are
+only safe to execute
+.Em if and only if
+the requesting party is trusted to see the response.
+Since none of these resources are intended to be used from web
+pages, it is important that web pages not be able to execute them
+.Em and
+observe the responses.
+.Pp
+Some of the resources provided by this service do change
+server-side state, specifically principal entries in the KDC's
+HDB.
+Those always require the use of POST, not GET.
+.Pp
+In a web browser context, pages from other origins will be able
+to attempt requests to this service, but should never be able to
+see the responses because browsers normally wouldn't allow that.
+Nonetheless, anti cross site request forgery (CSRF) protection
+may be desirable.
+.Pp
+This service provides the following CSRF protection features:
+.Bl -tag -width Ds -offset indent
+.It requests are rejected if they have a
+.Dq Referer
+(except the experimental /get-negotiate-token end-point)
+.It the service can be configured to require a header that would make the
+request not Dq simple
+.It GETs can be disabled (see options), thus requiring POSTs
+.It GETs can be required to have a CSRF token (see below)
+.It POSTs can be required to have a CSRF token
+.El
 .Pp
+The experimental
+.Ar /get-negotiate-token
+end-point, however, always accepts
+.Dq Referer
+requests.
+.Pp
+To obtain a CSRF token, first execute the request without the
+CSRF token, and the resulting error
+response will include a
+.Ar X-CSRF-Token
+response header.
+.Pp
+To execute a request with a CSRF token, first obtain a CSRF token
+as described above, then copy the token to the request as the
+value of the request's
+.Ar X-CSRF-Token
+header.
+.Pp
+The key for keying the CSRF token HMAC is that of the first
+current key for the
+.Sq WELLKNOWN/CSRFTOKEN
+principal for the realm being used.
+Every realm served by this service must have this principal.
+.Sh GETTING KEYTABS
 The
 .Ar /get-keys
 end-point accepts various parameters:
@@ -140,6 +232,14 @@ If the named principal(s) is (are) virtual, this will cause it
 .It Ar create=true
 If the named principal(s) does not (do not) exist, this will
 cause it (them) to be created.
+The default attributes for new principals created this way will
+be taken (except for the disabled attribute) from any containing
+virtual host-based service principal namespace that include a
+leading
+.Sq .
+in the hostname component, or from
+.Nm krb5.conf(5)
+(see the CONFIGURATION section).
 .It Ar rotate=true
 This will cause the keys of concrete principals to be rotated.
 .It Ar revoke=true
@@ -150,6 +250,31 @@ the target will not be able to be decrypted by the caller as it
 will not have the necessary keys.
 .El
 .Pp
+The HTTP
+.Nm Cache-Control
+header will be set on
+.Nm get-keys
+responses to
+.Dq Nm no-store ,
+and the
+.Nm max-age
+cache control parameter will be set to the least number of
+seconds until before any of the requested principal's keys could
+change.
+For virtual principals this will be either the time left until a
+quarter of the rotation period before the next rotation, or the
+time left until a
+quarter of the rotation period after the next rotation.
+For concrete principals this will be the time left to the first
+such principal's password expiration, or, if none of them have a
+configured password expiration time, then half of the
+.Nm new_service_key_delay
+configured in the
+.Nm [hdb]
+section of the
+.Nm krb5.conf(5)
+file.
+.Pp
 Authorization is handled via the same mechanism as in
 .Nm bx509d(8)
 which was originally intended to authorize certification requests
@@ -160,9 +285,10 @@ but using
 .Nm [ext_keytab]
 as the
 .Nm krb5.conf(5) section.
-Clients with host-based principals for the the host service can
-create and extract keys for their own service name, but otherwise
-a number of service names are not denied:
+Clients with host-based principals for the
+.Dq host
+service can create and extract keys for their own service name,
+but otherwise a number of service names are denied:
 .Bl -tag -width Ds -offset indent
 .It Dq host
 .It Dq root
@@ -207,6 +333,51 @@ Serves HTTP instead of HTTPS, accepting only looped-back connections.
 .Xc
 PORT
 .It Xo
+.Fl Fl allow-GET
+.Xc
+If given, then HTTP GET will be allowed for the various end-points
+other than
+.Ar /health .
+Otherwise only HEAD and POST will be allowed.
+By default GETs are allowed, but this will change soon.
+.It Xo
+.Fl Fl no-allow-GET
+.Xc
+If given then HTTP GETs will be rejected for the various
+end-points other than
+.Ar /health .
+.It Xo
+.Fl Fl csrf-protection-type= Ns Ar CSRF-PROTECTION-TYPE
+.Xc
+Possible values of
+.Ar CSRF-PROTECTION-TYPE
+are
+.Bl -bullet -compact -offset indent
+.It
+.Li GET-with-header
+.It
+.Li GET-with-token
+.It
+.Li POST-with-header
+.It
+.Li POST-with-token
+.El
+This may be given multiple times.
+The default is to require CSRF tokens for POST requests, and to
+require neither a non-simple header nor a CSRF token for GET
+requests.
+.Pp
+See
+.Sx CROSS-SITE REQUEST FORGERY PROTECTION .
+.It Xo
+.Fl Fl csrf-header= Ns Ar HEADER-NAME
+.Xc
+If given, then all requests other than to the
+.Ar /health
+service must have the given request
+.Ar HEADER-NAME
+set (the value is irrelevant).
+.It Xo
 .Fl Fl temp-dir= Ns Ar DIRECTORY
 .Xc
 Directory for temp files.
@@ -361,11 +532,69 @@ Authorizer configuration goes in
 in
 .Nm krb5.conf(5).  For example:
 .Pp
+.Bd -literal -offset indent
 [ext_keytab]
   simple_csr_authorizer_directory = /etc/krb5/simple_csr_authz
   ipc_csr_authorizer = {
     service = UNIX:/var/heimdal/csr_authorizer_sock
   }
+.Ed
+.Pp
+Configuration parameters specific to
+.Nm httpkadmind :
+.Bl -tag -width Ds -offset indent
+.It csr_authorizer_handles_svc_names = BOOL
+.It new_hostbased_service_principal_attributes = ...
+.El
+.Pp
+The
+.Nm [ext_keytab]
+.Nm get_keys_max_spns = NUMBER
+parameter can be used to specify a maximum number of principals whose
+keys can be retrieved in one
+.Nm GET
+of the
+.Nm /get-keys
+end-point.
+Defaults to 400.
+.Pp
+The
+.Nm [ext_keytab]
+.Nm new_hostbased_service_principal_attributes
+parameter may be used instead of virtual host-based service
+namespace principals to specify the attributes of new principals
+created by
+.Nm httpkadmind ,
+and its value is a hive with a service name then a hostname or
+namespace, and whose value is a set of attributes as given in the
+.Nm kadmin(1) modify
+command.
+For example:
+.Bd -literal -offset indent
+[ext_keytab]
+  new_hostbased_service_principal_attributes = {
+    host = {
+        a-particular-hostname.test.h5l.se = ok-as-delegate
+        .prod.test.h5l.se = ok-as-delegate
+    }
+  }
+.Ed
+.Pp
+which means that
+.Dq host/a-particular-hostname.test.h5l.se ,
+if created via
+.Nm httpkadmind ,
+will be allowed to get delegated credentials (ticket forwarding),
+and that hostnames matching the glob pattern
+.Dq host/*.prod.test.h5l.se ,
+if created via
+.Nm httpkadmind ,
+will also allowed to get delegated credentials.
+All host-based service principals created via
+.Nm httpkadmind
+not matchining any
+.Nm new_hostbased_service_principal_attributes
+service namespaces will have the empty attribute set.
 .Sh EXAMPLES
 To start
 .Nm httpkadmind
index 0e31b4044c3ca2577a28904b5e558cc4dbf1e7ef..068b5acbf905790b44d98672e7c803c8a5896d9c 100644 (file)
@@ -128,6 +128,12 @@ typedef enum MHD_Result heim_mhd_result;
  *        here.
  */
 
+struct free_tend_list {
+    void *freeme1;
+    void *freeme2;
+    struct free_tend_list *next;
+};
+
 /* Our request description structure */
 typedef struct kadmin_request_desc {
     HEIM_SVC_REQUEST_DESC_COMMON_ELEMENTS;
@@ -165,6 +171,8 @@ typedef struct kadmin_request_desc {
      * be damned.
      */
     hx509_request req;          /* For authz only */
+    struct free_tend_list *free_list;
+    struct MHD_PostProcessor *pp;
     heim_array_t service_names;
     heim_array_t hostnames;
     heim_array_t spns;
@@ -176,7 +184,11 @@ typedef struct kadmin_request_desc {
     char *keytab_name;
     char *freeme1;
     char *enctypes;
+    char *cache_control;
+    char *csrf_token;
     const char *method;
+    krb5_timestamp pw_end;
+    size_t post_data_size;
     unsigned int response_set:1;
     unsigned int materialize:1;
     unsigned int rotate_now:1;
@@ -305,8 +317,18 @@ get_krb5_context(krb5_context *contextp)
     return ret;
 }
 
+typedef enum {
+    CSRF_PROT_UNSPEC            = 0,
+    CSRF_PROT_GET_WITH_HEADER   = 1,
+    CSRF_PROT_GET_WITH_TOKEN    = 2,
+    CSRF_PROT_POST_WITH_HEADER  = 8,
+    CSRF_PROT_POST_WITH_TOKEN   = 16,
+} csrf_protection_type;
+
+static csrf_protection_type csrf_prot_type = CSRF_PROT_UNSPEC;
 static int port = -1;
 static int help_flag;
+static int allow_GET_flag = -1;
 static int daemonize;
 static int daemon_child_fd = -1;
 static int local_hdb;
@@ -317,6 +339,8 @@ static int version_flag;
 static int reverse_proxied_flag;
 static int thread_per_client_flag;
 struct getarg_strings audiences;
+static getarg_strings csrf_prot_type_strs;
+static const char *csrf_header = "X-CSRF";
 static const char *cert_file;
 static const char *priv_key_file;
 static const char *cache_dir;
@@ -425,7 +449,7 @@ out:
 
 static krb5_error_code resp(kadmin_request_desc, int, krb5_error_code,
                             enum MHD_ResponseMemoryMode, const char *,
-                            const void *, size_t, const char *, const char *);
+                            const void *, size_t, const char *);
 static krb5_error_code bad_req(kadmin_request_desc, krb5_error_code, int,
                                const char *, ...)
                                HEIMDAL_PRINTF_ATTRIBUTE((__printf__, 4, 5));
@@ -434,7 +458,7 @@ static krb5_error_code bad_enomem(kadmin_request_desc, krb5_error_code);
 static krb5_error_code bad_400(kadmin_request_desc, krb5_error_code, const char *);
 static krb5_error_code bad_401(kadmin_request_desc, const char *);
 static krb5_error_code bad_403(kadmin_request_desc, krb5_error_code, const char *);
-static krb5_error_code bad_404(kadmin_request_desc, const char *);
+static krb5_error_code bad_404(kadmin_request_desc, krb5_error_code, const char *);
 static krb5_error_code bad_405(kadmin_request_desc, const char *);
 /*static krb5_error_code bad_500(kadmin_request_desc, krb5_error_code, const char *);*/
 static krb5_error_code bad_503(kadmin_request_desc, krb5_error_code, const char *);
@@ -635,8 +659,7 @@ resp(kadmin_request_desc r,
      const char *content_type,
      const void *body,
      size_t bodylen,
-     const char *token,
-     const char *csrf)
+     const char *token)
 {
     struct MHD_Response *response;
     int mret = MHD_YES;
@@ -657,8 +680,35 @@ resp(kadmin_request_desc r,
                                                rmmode);
     if (response == NULL)
         return -1;
-    mret = MHD_add_response_header(response, MHD_HTTP_HEADER_CACHE_CONTROL,
-                                   "no-store, max-age=0");
+    mret = MHD_add_response_header(response, MHD_HTTP_HEADER_AGE, "0");
+    if (mret == MHD_YES && http_status_code == MHD_HTTP_OK) {
+        krb5_timestamp now;
+
+        free(r->cache_control);
+        r->cache_control = NULL;
+        krb5_timeofday(r->context, &now);
+        if (r->pw_end && r->pw_end > now) {
+            if (asprintf(&r->cache_control, "no-store, max-age=%lld",
+                         (long long)r->pw_end - now) == -1 ||
+                r->cache_control == NULL)
+                /* Soft handling of ENOMEM here */
+                mret = MHD_add_response_header(response,
+                                               MHD_HTTP_HEADER_CACHE_CONTROL,
+                                               "no-store, max-age=3600");
+            else
+                mret = MHD_add_response_header(response,
+                                               MHD_HTTP_HEADER_CACHE_CONTROL,
+                                               r->cache_control);
+
+        } else
+            mret = MHD_add_response_header(response,
+                                           MHD_HTTP_HEADER_CACHE_CONTROL,
+                                           "no-store, max-age=0");
+    } else {
+        /* Shouldn't happen */
+        mret = MHD_add_response_header(response, MHD_HTTP_HEADER_CACHE_CONTROL,
+                                       "no-store, max-age=0");
+    }
     if (mret == MHD_YES && http_status_code == MHD_HTTP_UNAUTHORIZED) {
         size_t i;
 
@@ -680,10 +730,10 @@ resp(kadmin_request_desc r,
             http_status_code = MHD_HTTP_SERVICE_UNAVAILABLE;
     }
 
-    if (mret == MHD_YES && csrf)
+    if (mret == MHD_YES && r->csrf_token)
         mret = MHD_add_response_header(response,
                                        "X-CSRF-Token",
-                                       csrf);
+                                       r->csrf_token);
 
     if (mret == MHD_YES && content_type) {
         mret = MHD_add_response_header(response,
@@ -722,7 +772,7 @@ bad_reqv(kadmin_request_desc r,
         if (context)
             krb5_log_msg(context, logfac, 1, NULL, "Out of memory");
         return resp(r, http_status_code, code, MHD_RESPMEM_PERSISTENT,
-                    NULL, fmt, BODYLEN_IS_STRLEN, NULL, NULL);
+                    NULL, fmt, BODYLEN_IS_STRLEN, NULL);
     }
 
     if (code) {
@@ -749,11 +799,11 @@ bad_reqv(kadmin_request_desc r,
             krb5_log_msg(context, logfac, 1, NULL, "Out of memory");
         return resp(r, MHD_HTTP_SERVICE_UNAVAILABLE, ENOMEM,
                     MHD_RESPMEM_PERSISTENT, NULL,
-                    "Out of memory", BODYLEN_IS_STRLEN, NULL, NULL);
+                    "Out of memory", BODYLEN_IS_STRLEN, NULL);
     }
 
     ret = resp(r, http_status_code, code, MHD_RESPMEM_MUST_COPY,
-               NULL, msg, BODYLEN_IS_STRLEN, NULL, NULL);
+               NULL, msg, BODYLEN_IS_STRLEN, NULL);
     free(formatted);
     free(msg);
     return ret == -1 ? -1 : code;
@@ -801,9 +851,9 @@ bad_403(kadmin_request_desc r, krb5_error_code ret, const char *reason)
 }
 
 static krb5_error_code
-bad_404(kadmin_request_desc r, const char *name)
+bad_404(kadmin_request_desc r, krb5_error_code ret, const char *name)
 {
-    return bad_req(r, ENOENT, MHD_HTTP_NOT_FOUND,
+    return bad_req(r, ret, MHD_HTTP_NOT_FOUND,
                    "Resource not found: %s", name);
 }
 
@@ -814,6 +864,13 @@ bad_405(kadmin_request_desc r, const char *method)
                    "Method not supported: %s", method);
 }
 
+static krb5_error_code
+bad_413(kadmin_request_desc r)
+{
+    return bad_req(r, E2BIG, MHD_HTTP_METHOD_NOT_ALLOWED,
+                   "POST request body too large");
+}
+
 static krb5_error_code
 bad_method_want_POST(kadmin_request_desc r)
 {
@@ -859,7 +916,7 @@ good_ext_keytab(kadmin_request_desc r)
         return bad_503(r, ret, "Could not recover keytab from temp file");
 
     ret = resp(r, MHD_HTTP_OK, 0, MHD_RESPMEM_MUST_COPY,
-               "application/octet-stream", body, bodylen, NULL, NULL);
+               "application/octet-stream", body, bodylen, NULL);
     free(body);
     return ret;
 }
@@ -1215,6 +1272,93 @@ make_kstuple(krb5_context context,
     return *kstuple ? 0 :krb5_enomem(context);
 }
 
+/* Copied from kadmin/util.c */
+struct units kdb_attrs[] = {
+    { "no-auth-data-reqd",      KRB5_KDB_NO_AUTH_DATA_REQUIRED },
+    { "disallow-client",        KRB5_KDB_DISALLOW_CLIENT },
+    { "virtual",                KRB5_KDB_VIRTUAL },
+    { "virtual-keys",           KRB5_KDB_VIRTUAL_KEYS },
+    { "allow-digest",           KRB5_KDB_ALLOW_DIGEST },
+    { "allow-kerberos4",        KRB5_KDB_ALLOW_KERBEROS4 },
+    { "trusted-for-delegation", KRB5_KDB_TRUSTED_FOR_DELEGATION },
+    { "ok-as-delegate",         KRB5_KDB_OK_AS_DELEGATE },
+    { "new-princ",              KRB5_KDB_NEW_PRINC },
+    { "support-desmd5",         KRB5_KDB_SUPPORT_DESMD5 },
+    { "pwchange-service",       KRB5_KDB_PWCHANGE_SERVICE },
+    { "disallow-svr",           KRB5_KDB_DISALLOW_SVR },
+    { "requires-pw-change",     KRB5_KDB_REQUIRES_PWCHANGE },
+    { "requires-hw-auth",       KRB5_KDB_REQUIRES_HW_AUTH },
+    { "requires-pre-auth",      KRB5_KDB_REQUIRES_PRE_AUTH },
+    { "disallow-all-tix",       KRB5_KDB_DISALLOW_ALL_TIX },
+    { "disallow-dup-skey",      KRB5_KDB_DISALLOW_DUP_SKEY },
+    { "disallow-proxiable",     KRB5_KDB_DISALLOW_PROXIABLE },
+    { "disallow-renewable",     KRB5_KDB_DISALLOW_RENEWABLE },
+    { "disallow-tgt-based",     KRB5_KDB_DISALLOW_TGT_BASED },
+    { "disallow-forwardable",   KRB5_KDB_DISALLOW_FORWARDABLE },
+    { "disallow-postdated",     KRB5_KDB_DISALLOW_POSTDATED },
+    { NULL, 0 }
+};
+
+/*
+ * Determine the default/allowed attributes for some new principal.
+ */
+static krb5_flags
+create_attributes(kadmin_request_desc r, krb5_const_principal p)
+{
+    krb5_error_code ret;
+    const char *srealm = krb5_principal_get_realm(r->context, p);
+    const char *svc;
+    const char *hn;
+
+    /* Has to be a host-based service principal (for now) */
+    if (krb5_principal_get_num_comp(r->context, p) != 2)
+        return 0;
+
+    hn = krb5_principal_get_comp_string(r->context, p, 1);
+    svc = krb5_principal_get_comp_string(r->context, p, 0);
+
+    while (hn && strchr(hn, '.') != NULL) {
+        kadm5_principal_ent_rec nsprinc;
+        krb5_principal nsp;
+        uint64_t a = 0;
+        const char *as;
+
+        /* Try finding a virtual host-based service principal namespace */
+        memset(&nsprinc, 0, sizeof(nsprinc));
+        ret = krb5_make_principal(r->context, &nsp, srealm,
+                                  KRB5_WELLKNOWN_NAME, HDB_WK_NAMESPACE,
+                                  svc, hn, NULL);
+        if (ret == 0)
+            ret = kadm5_get_principal(r->kadm_handle, nsp, &nsprinc,
+                                      KADM5_PRINCIPAL | KADM5_ATTRIBUTES);
+        krb5_free_principal(r->context, nsp);
+        if (ret == 0) {
+            /* Found one; use it even if disabled, but drop that attribute */
+            a = nsprinc.attributes & ~KRB5_KDB_DISALLOW_ALL_TIX;
+            kadm5_free_principal_ent(r->kadm_handle, &nsprinc);
+            return a;
+        }
+
+        /* Fallback on krb5.conf */
+        as = krb5_config_get_string(r->context, NULL, "ext_keytab",
+                                    "new_hostbased_service_principal_attributes",
+                                    svc, hn, NULL);
+        if (as) {
+            a = parse_flags(as, kdb_attrs, 0);
+            if (a == (uint64_t)-1) {
+                krb5_warnx(r->context, "Invalid value for [ext_keytab] "
+                           "new_hostbased_service_principal_attributes");
+                return 0;
+            }
+            return a;
+        }
+
+        hn = strchr(hn + 1, '.');
+    }
+
+    return 0;
+}
+
 /*
  * Get keys for one principal.
  *
@@ -1229,7 +1373,8 @@ get_keys1(kadmin_request_desc r, const char *pname)
     krb5_principal p = NULL;
     uint32_t mask =
         KADM5_PRINCIPAL | KADM5_KVNO | KADM5_MAX_LIFE | KADM5_MAX_RLIFE |
-        KADM5_ATTRIBUTES | KADM5_KEY_DATA | KADM5_TL_DATA;
+        KADM5_PW_EXPIRATION | KADM5_ATTRIBUTES | KADM5_KEY_DATA |
+        KADM5_TL_DATA;
     uint32_t create_mask = mask & ~(KADM5_KEY_DATA | KADM5_TL_DATA);
     size_t nkstuple = 0;
     int change = 0;
@@ -1270,6 +1415,9 @@ get_keys1(kadmin_request_desc r, const char *pname)
     if (ret == KADM5_UNK_PRINC && r->create) {
         char pw[128];
 
+        memset(&princ, 0, sizeof(princ));
+        princ.attributes = create_attributes(r, p);
+
         if (read_only)
             ret = KADM5_READ_ONLY;
         else
@@ -1281,7 +1429,6 @@ get_keys1(kadmin_request_desc r, const char *pname)
             ret = get_kadm_handle(r->context, r->realm, 1 /* want_write */,
                                   &r->kadm_handle);
         }
-        memset(&princ, 0, sizeof(princ));
         /*
          * Some software is allergic to kvno 1, assuming that kvno 1 implies
          * half-baked service principal.  We've some vague recollection of
@@ -1304,9 +1451,6 @@ get_keys1(kadmin_request_desc r, const char *pname)
     } else if (ret == 0 && r->materialize &&
                (princ.attributes & KRB5_KDB_VIRTUAL)) {
 
-#ifndef MATERIALIZE_NOTYET
-        ret = ENOTSUP;
-#else
         if (read_only)
             ret = KADM5_READ_ONLY;
         else
@@ -1335,7 +1479,6 @@ get_keys1(kadmin_request_desc r, const char *pname)
         if (ret == 0)
             ret = kadm5_create_principal(r->kadm_handle, &princ, mask, "");
         refetch = 1;
-#endif
     } /* else create/materialize q-params are superfluous */
 
     /* Handle rotate / revoke options */
@@ -1384,6 +1527,36 @@ get_keys1(kadmin_request_desc r, const char *pname)
 
     if (ret == 0)
         ret = write_keytab(r, &princ, pname);
+
+    if (ret == 0) {
+        /*
+         * We will use the principal's password expiration to work out the
+         * value for the max-age Cache-Control.
+         *
+         * Virtual service principals will have their `pw_expiration' set to a
+         * time when the client should refetch keys.
+         *
+         * Concrete service principals will generally not have a non-zero
+         * `pw_expiration', but if we have a new_service_key_delay, then we'll
+         * use half of it as the max-age Cache-Control.
+         */
+        if (princ.pw_expiration == 0) {
+            krb5_timestamp nskd =
+                krb5_config_get_time_default(r->context, NULL, 0, "hdb",
+                                             "new_service_key_delay", NULL);
+            if (nskd)
+                princ.pw_expiration = time(NULL) + (nskd >> 1);
+        }
+
+        /*
+         * This service can be used to fetch more than one principal's keys, so
+         * the max-age Cache-Control should be derived from the soonest-
+         * "expiring" principal.
+         */
+        if (r->pw_end == 0 ||
+            (princ.pw_expiration < r->pw_end && princ.pw_expiration > time(NULL)))
+            r->pw_end = princ.pw_expiration;
+    }
     if (freeit)
         kadm5_free_principal_ent(r->kadm_handle, &princ);
     krb5_free_principal(r->context, p);
@@ -1398,7 +1571,7 @@ static krb5_error_code check_csrf(kadmin_request_desc);
  * When this returns a response will have been set.
  */
 static krb5_error_code
-get_keysN(kadmin_request_desc r, const char *method)
+get_keysN(kadmin_request_desc r)
 {
     krb5_error_code ret;
     size_t nhosts;
@@ -1411,11 +1584,17 @@ get_keysN(kadmin_request_desc r, const char *method)
     if (ret)
         return ret; /* authorize_req() calls bad_req() on error */
 
+    /*
+     * If we have a r->kadm_handle already it's because we validated a CSRF
+     * token.  It may not be a handle to a realm we wanted though.
+     */
+    if (r->kadm_handle)
+        kadm5_destroy(r->kadm_handle);
+    r->kadm_handle = NULL;
     ret = get_kadm_handle(r->context, r->realm ? r->realm : realm,
                           0 /* want_write */, &r->kadm_handle);
-
-    if (strcmp(method, "POST") == 0 && (ret = check_csrf(r)))
-        return ret; /* check_csrf() calls bad_req() on error */
+    if (ret)
+        return bad_404(r, ret, "Could not connect to realm");
 
     nhosts = heim_array_get_length(r->hostnames);
     nsvcs = heim_array_get_length(r->service_names);
@@ -1436,8 +1615,9 @@ get_keysN(kadmin_request_desc r, const char *method)
             return bad_503(r, ret, "Out of memory");
     }
 
-    /* FIXME: Make this configurable */
-    if (nspns + nsvcs * nhosts > 40)
+    if (nspns + nsvcs * nhosts >
+        krb5_config_get_int_default(r->context, NULL, 400,
+                                    "ext_keytab", "get_keys_max_spns", NULL))
         return bad_403(r, EINVAL, "Requested keys for too many principals");
 
     ret = make_keytab(r);
@@ -1485,7 +1665,7 @@ get_keysN(kadmin_request_desc r, const char *method)
             krb5_log_msg(r->context, logfac, 1, NULL,
                          "Redirect %s to primary server", r->cname);
             return resp(r, MHD_HTTP_TEMPORARY_REDIRECT, KADM5_READ_ONLY,
-                        MHD_RESPMEM_PERSISTENT, NULL, "", 0, NULL, NULL);
+                        MHD_RESPMEM_PERSISTENT, NULL, "", 0, NULL);
         } else {
             krb5_log_msg(r->context, logfac, 1, NULL, "HDB is read-only");
             return bad_403(r, ret, "HDB is read-only");
@@ -1518,25 +1698,33 @@ addr_to_string(krb5_context context,
         snprintf(str, len, "<family=%d>", addr->sa_family);
 }
 
+static void clean_req_desc(kadmin_request_desc);
+
 static krb5_error_code
 set_req_desc(struct MHD_Connection *connection,
              const char *method,
              const char *url,
-             kadmin_request_desc r)
+             kadmin_request_desc *rp)
 {
     const union MHD_ConnectionInfo *ci;
+    kadmin_request_desc r;
     const char *token;
     krb5_error_code ret;
 
-    memset(r, 0, sizeof(*r));
-    (void) gettimeofday(&r->tv_start, NULL);
+    *rp = NULL;
+    if ((r = calloc(1, sizeof(*r))) == NULL)
+        return ENOMEM;
 
-    if ((ret = get_krb5_context(&r->context)))
+    (void) gettimeofday(&r->tv_start, NULL);
+    if ((ret = get_krb5_context(&r->context))) {
+        free(r);
         return ret;
+    }
     /* HEIM_SVC_REQUEST_DESC_COMMON_ELEMENTS fields */
     r->request.data = "<HTTP-REQUEST>";
     r->request.length = sizeof("<HTTP-REQUEST>");
     r->from = r->frombuf;
+    r->free_list = NULL;
     r->config = NULL;
     r->logf = logfac;
     r->reqtype = url;
@@ -1546,6 +1734,7 @@ set_req_desc(struct MHD_Connection *connection,
     r->cname = NULL;
     r->addr = NULL;
     r->kv = heim_dict_create(10);
+    r->pp = NULL;
     r->attributes = heim_dict_create(1);
     /* Our fields */
     r->connection = connection;
@@ -1556,6 +1745,7 @@ set_req_desc(struct MHD_Connection *connection,
     r->spns = heim_array_create();
     r->keytab_name = NULL;
     r->enctypes = NULL;
+    r->cache_control = NULL;
     r->freeme1 = NULL;
     r->method = method;
     r->cprinc = NULL;
@@ -1590,6 +1780,10 @@ set_req_desc(struct MHD_Connection *connection,
         krb5_log_msg(r->context, logfac, 1, NULL, "Out of memory");
         ret = r->error_code = ENOMEM;
     }
+    if (ret == 0)
+        *rp = r;
+    else
+        clean_req_desc(r);
     return ret;
 }
 
@@ -1605,32 +1799,49 @@ clean_req_desc(kadmin_request_desc r)
         (void) unlink(strchr(r->keytab_name, ':') + 1);
     if (r->kadm_handle)
         kadm5_destroy(r->kadm_handle);
+    if (r->pp)
+        MHD_destroy_post_processor(r->pp);
     hx509_request_free(&r->req);
     heim_release(r->service_names);
+    heim_release(r->attributes);
     heim_release(r->hostnames);
     heim_release(r->reason);
     heim_release(r->spns);
     heim_release(r->kv);
     krb5_free_principal(r->context, r->cprinc);
+    free(r->cache_control);
     free(r->keytab_name);
+    free(r->csrf_token);
     free(r->enctypes);
     free(r->freeme1);
     free(r->cname);
     free(r->sname);
+    free(r->realm);
+    free(r);
+}
+
+static void
+cleanup_req(void *cls,
+            struct MHD_Connection *connection,
+            void **con_cls,
+            enum MHD_RequestTerminationCode toe)
+{
+    kadmin_request_desc r = *con_cls;
+
+    (void)cls;
+    (void)connection;
+    (void)toe;
+    clean_req_desc(r);
+    *con_cls = NULL;
 }
 
 /* Implements GETs of /get-keys */
 static krb5_error_code
-get_keys(kadmin_request_desc r, const char *method)
+get_keys(kadmin_request_desc r)
 {
-    krb5_error_code ret;
-
-    if ((ret = validate_token(r)))
-        return ret; /* validate_token() calls bad_req() */
     if (r->cname == NULL || r->cprinc == NULL)
-        return bad_403(r, EINVAL,
-                       "Could not extract principal name from token");
-    return get_keysN(r, method); /* Sets an HTTP response */
+        return bad_401(r, "Could not extract principal name from token");
+    return get_keysN(r); /* Sets an HTTP response */
 }
 
 /* Implements GETs of /get-config */
@@ -1649,11 +1860,8 @@ get_config(kadmin_request_desc r)
     void *body = "include /etc/krb5.conf\n";
     int freeit = 0;
 
-    if ((ret = validate_token(r)))
-        return ret; /* validate_token() calls bad_req() */
     if (r->cname == NULL || r->cprinc == NULL)
-        return bad_403(r, EINVAL,
-                       "Could not extract principal name from token");
+        return bad_401(r, "Could not extract principal name from token");
     /*
      * No authorization needed -- configs are public.  Though we do require
      * authentication (above).
@@ -1686,7 +1894,7 @@ get_config(kadmin_request_desc r)
             }
         } else {
             r->error_code = ret;
-            return bad_404(r, "/get-config");
+            return bad_404(r, ret, "/get-config");
         }
     }
 
@@ -1694,7 +1902,7 @@ get_config(kadmin_request_desc r)
         krb5_log_msg(r->context, logfac, 1, NULL,
                      "Returned krb5.conf contents to %s", r->cname);
         ret = resp(r, MHD_HTTP_OK, 0, MHD_RESPMEM_MUST_COPY,
-                   "application/text", body, bodylen, NULL, NULL);
+                   "application/text", body, bodylen, NULL);
     } else {
         ret = bad_503(r, ret, "Could not retrieve principal configuration");
     }
@@ -1720,7 +1928,9 @@ mac_csrf_token(kadmin_request_desc r, krb5_storage *sp)
     memset(&princ, 0, sizeof(princ));
     ret = krb5_storage_to_data(sp, &data);
     if (r->kadm_handle == NULL)
-        ret = get_kadm_handle(r->context, r->realm, 0 /* want_write */,
+        ret = get_kadm_handle(r->context,
+                              r->realm ? r->realm : realm,
+                              0 /* want_write */,
                               &r->kadm_handle);
     if (ret == 0)
         ret = krb5_make_principal(r->context, &p,
@@ -1776,7 +1986,6 @@ make_csrf_token(kadmin_request_desc r,
                 char **token,
                 int64_t *age)
 {
-    static HEIMDAL_THREAD_LOCAL char tokenbuf[128]; /* See below, be sad */
     krb5_error_code ret = 0;
     unsigned char given_decoded[128];
     krb5_storage *sp = NULL;
@@ -1827,17 +2036,6 @@ make_csrf_token(kadmin_request_desc r,
     if (ret == 0 &&
         (dlen = rk_base64_encode(data.data, data.length, token)) < 0)
         ret = errno;
-    if (ret == 0 && dlen >= sizeof(tokenbuf))
-        ret = ERANGE;
-    if (ret == 0) {
-        /*
-         * Work around for older versions of libmicrohttpd do not strdup()ing
-         * response header values.
-         */
-        memcpy(tokenbuf, *token, dlen);
-        free(*token);
-        *token = tokenbuf;
-    }
     krb5_storage_free(sp);
     krb5_data_free(&data);
     return ret;
@@ -1854,11 +2052,28 @@ check_csrf(kadmin_request_desc r)
     const char *given;
     int64_t age;
     size_t givenlen, expectedlen;
-    char *expected = NULL;
+
+    if ((((csrf_prot_type & CSRF_PROT_GET_WITH_HEADER) &&
+          strcmp(r->method, "GET") == 0) ||
+         ((csrf_prot_type & CSRF_PROT_POST_WITH_HEADER) &&
+          strcmp(r->method, "POST") == 0)) &&
+        MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
+                                    csrf_header) == NULL) {
+        ret = bad_req(r, EACCES, MHD_HTTP_FORBIDDEN,
+                      "Request must have header \"%s\"", csrf_header);
+        return ret == -1 ? MHD_NO : MHD_YES;
+    }
+
+    if (strcmp(r->method, "GET") == 0 &&
+        !(csrf_prot_type & CSRF_PROT_GET_WITH_TOKEN))
+        return 0;
+    if (strcmp(r->method, "POST") == 0 &&
+        !(csrf_prot_type & CSRF_PROT_POST_WITH_TOKEN))
+        return 0;
 
     given = MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
                                         "X-CSRF-Token");
-    ret = make_csrf_token(r, given, &expected, &age);
+    ret = make_csrf_token(r, given, &r->csrf_token, &age);
     if (ret)
         return bad_503(r, ret, "Could not create a CSRF token");
     /*
@@ -1868,15 +2083,14 @@ check_csrf(kadmin_request_desc r)
     if (given == NULL) {
         (void) resp(r, MHD_HTTP_FORBIDDEN, ENOSYS, MHD_RESPMEM_PERSISTENT,
                     NULL, "CSRF token needed; copy the X-CSRF-Token: response "
-                    "header to your next POST", BODYLEN_IS_STRLEN, NULL,
-                    expected);
+                    "header to your next POST", BODYLEN_IS_STRLEN, NULL);
         return ENOSYS;
     }
 
     /* Validate the CSRF token for this request */
     givenlen = strlen(given);
-    expectedlen = strlen(expected);
-    if (givenlen != expectedlen || ct_memcmp(given, expected, givenlen)) {
+    expectedlen = strlen(r->csrf_token);
+    if (givenlen != expectedlen || ct_memcmp(given, r->csrf_token, givenlen)) {
         (void) bad_403(r, EACCES, "Invalid CSRF token");
         return EACCES;
     }
@@ -1892,15 +2106,60 @@ health(const char *method, kadmin_request_desc r)
 {
     if (strcmp(method, "HEAD") == 0) {
         return resp(r, MHD_HTTP_OK, 0, MHD_RESPMEM_PERSISTENT, NULL, "", 0,
-                    NULL, NULL);
+                    NULL);
     }
     return resp(r, MHD_HTTP_OK, 0, MHD_RESPMEM_PERSISTENT, NULL,
                 "To determine the health of the service, use the /get-config "
-                "end-point.\n", BODYLEN_IS_STRLEN, NULL, NULL);
+                "end-point.\n", BODYLEN_IS_STRLEN, NULL);
 
 }
 
-/* Implements the entirety of this REST service */
+static heim_mhd_result
+ip(void *cls,
+   enum MHD_ValueKind kind,
+   const char *key,
+   const char *content_name,
+   const char *content_type,
+   const char *transfer_encoding,
+   const char *val,
+   uint64_t off,
+   size_t size)
+{
+    kadmin_request_desc r = cls;
+    struct free_tend_list *ftl = calloc(1, sizeof(*ftl));
+    char *keydup = strdup(key);
+    char *valdup = strndup(val, size);
+
+    (void)content_name;         /* MIME attachment name */
+    (void)content_type;
+    (void)transfer_encoding;
+    (void)off;                  /* Offset in POST data */
+
+    /* We're going to MHD_set_connection_value(), but we need copies */
+    if (ftl == NULL || keydup == NULL || valdup == NULL) {
+        free(ftl);
+        free(keydup);
+        return MHD_NO;
+    }
+    ftl->freeme1 = keydup;
+    ftl->freeme2 = valdup;
+    ftl->next = r->free_list;
+    r->free_list = ftl;
+
+    return MHD_set_connection_value(r->connection, MHD_GET_ARGUMENT_KIND,
+                                    keydup, valdup);
+}
+
+typedef krb5_error_code (*handler)(struct kadmin_request_desc *);
+
+struct route {
+    const char *local_part;
+    handler h;
+} routes[] = {
+    { "/get-keys", get_keys },
+    { "/get-config", get_config },
+};
+
 static heim_mhd_result
 route(void *cls,
       struct MHD_Connection *connection,
@@ -1911,50 +2170,131 @@ route(void *cls,
       size_t *upload_data_size,
       void **ctx)
 {
-    static int aptr = 0;
-    struct kadmin_request_desc r;
+    struct kadmin_request_desc *r = *ctx;
+    size_t i;
     int ret;
 
-    if (*ctx == NULL) {
+    if (r == NULL) {
         /*
          * This is the first call, right after headers were read.
          *
          * We must return quickly so that any 100-Continue might be sent with
-         * celerity.
+         * celerity.  We want to make sure to send any 401s early, so we check
+         * WWW-Authenticate now, not later.
          *
-         * We'll get called again to really do the processing.  If we handled
-         * POSTs then we'd also get called with upload_data != NULL between the
-         * first and last calls.  We need to keep no state between the first
-         * and last calls, but we do need to distinguish first and last call,
-         * so we use the ctx argument for this.
+         * We'll get called again to really do the processing.  If we're
+         * handling a POST then we'll also get called with upload_data != NULL,
+         * possibly multiple times.
          */
-        *ctx = &aptr;
+        if ((ret = set_req_desc(connection, method, url, &r))) {
+            return
+                bad_503(r, ret, "Could not initialize request state") == -1
+                    ? MHD_NO : MHD_YES;
+        }
+        *ctx = r;
+
+        /*
+         * All requests other than /health require authentication and CSRF
+         * protection.
+         */
+        if (strcmp(url, "/health") == 0)
+            return MHD_YES;
+
+        /* Authenticate and do CSRF protection */
+        ret = validate_token(r);
+        if (ret == 0)
+            ret = check_csrf(r);
+
+        /*
+         * As this is the initial call to this handler, we must return now.
+         *
+         * If authentication or CSRF protection failed then we'll already have
+         * enqueued a 401, 403, or 5xx response and then we're done.
+         *
+         * If both authentication and CSRF protection succeeded then no
+         * response has been queued up and we'll get called again to finally
+         * process the request, then this entire if block will not be executed.
+         */
+        return ret == -1 ? MHD_NO : MHD_YES;
+    }
+
+    /* Validate HTTP method */
+    if (strcmp(method, "GET") != 0 &&
+        strcmp(method, "POST") != 0 &&
+        strcmp(method, "HEAD") != 0) {
+        return bad_405(r, method) == -1 ? MHD_NO : MHD_YES;
+    }
+
+    if ((strcmp(method, "HEAD") == 0 || strcmp(method, "GET") == 0) &&
+        (strcmp(url, "/health") == 0 || strcmp(url, "/") == 0)) {
+        /* /health end-point -- no authentication, no CSRF, no nothing */
+        return health(method, r) == -1 ? MHD_NO : MHD_YES;
+    }
+
+    if (strcmp(method, "POST") == 0 && *upload_data_size != 0) {
+        /*
+         * Consume all the POST body and set form data as MHD_GET_ARGUMENT_KIND
+         * (as if they had been URI query parameters).
+         *
+         * We have to do this before we can MHD_queue_response() as MHD will
+         * not consume the rest of the request body on its own, so it's an
+         * error to MHD_queue_response() before we've done this, and if we do
+         * then MHD just closes the connection.
+         *
+         * 4KB should be more than enough buffer space for all the keys we
+         * expect.
+         */
+        if (r->pp == NULL)
+            r->pp = MHD_create_post_processor(connection, 4096, ip, r);
+        if (r->pp == NULL) {
+            ret = bad_503(r, errno ? errno : ENOMEM,
+                          "Could not consume POST data");
+            return ret == -1 ? MHD_NO : MHD_YES;
+        }
+        if (r->post_data_size + *upload_data_size > 1UL<<17) {
+            return bad_413(r) == -1 ? MHD_NO : MHD_YES;
+        }
+        r->post_data_size += *upload_data_size;
+        if (MHD_post_process(r->pp, upload_data,
+                             *upload_data_size) == MHD_NO) {
+            ret = bad_503(r, errno ? errno : ENOMEM,
+                          "Could not consume POST data");
+            return ret == -1 ? MHD_NO : MHD_YES;
+        }
+        *upload_data_size = 0;
         return MHD_YES;
     }
 
     /*
-     * Note that because we attempt to connect to the HDB in set_req_desc(),
-     * this early 503 if we fail to serves to do all of what /health should do.
+     * Either this is a HEAD, a GET, or a POST whose request body has now been
+     * received completely and processed.
      */
-    if ((ret = set_req_desc(connection, method, url, &r)))
-        return bad_503(&r, ret, "Could not initialize request state");
-    if ((strcmp(method, "HEAD") == 0 || strcmp(method, "GET") == 0) &&
-        (strcmp(url, "/health") == 0 || strcmp(url, "/") == 0)) {
-        ret = health(method, &r);
-    } else if (strcmp(method, "GET") != 0 && strcmp(method, "POST") != 0) {
-        ret = bad_405(&r, method);
-    } else if (strcmp(url, "/get-keys") == 0) {
-        ret = get_keys(&r, method);
-    } else if (strcmp(url, "/get-config") == 0) {
-        if (strcmp(method, "GET") != 0)
-            ret = bad_405(&r, method);
+
+    /* Allow GET? */
+    if (strcmp(method, "GET") == 0 && !allow_GET_flag) {
+        /* No */
+        return bad_405(r, method) == -1 ? MHD_NO : MHD_YES;
+    }
+
+    for (i = 0; i < sizeof(routes)/sizeof(routes[0]); i++) {
+        if (strcmp(url, routes[i].local_part) != 0)
+            continue;
+        if (MHD_lookup_connection_value(r->connection,
+                                        MHD_HEADER_KIND,
+                                        "Referer") != NULL) {
+            ret = bad_req(r, EACCES, MHD_HTTP_FORBIDDEN,
+                          "GET from browser not allowed");
+            return ret == -1 ? MHD_NO : MHD_YES;
+        }
+        if (strcmp(method, "HEAD") == 0)
+            ret = resp(r, MHD_HTTP_OK, 0, MHD_RESPMEM_PERSISTENT, NULL, "", 0,
+                       NULL);
         else
-            ret = get_config(&r);
-    } else {
-        ret = bad_404(&r, url);
+            ret = routes[i].h(r);
+        return ret == -1 ? MHD_NO : MHD_YES;
     }
 
-    clean_req_desc(&r);
+    ret = bad_404(r, ENOENT, url);
     return ret == -1 ? MHD_NO : MHD_YES;
 }
 
@@ -1963,6 +2303,10 @@ static struct getargs args[] = {
     { "version", '\0', arg_flag, &version_flag, "Print version", NULL },
     { NULL, 'H', arg_strings, &audiences,
         "expected token audience(s) of the service", "HOSTNAME" },
+    { "allow-GET", 0, arg_negative_flag,
+        &allow_GET_flag, NULL, NULL },
+    { "csrf-header", 0, arg_string, &csrf_header,
+        "required request header", "HEADER-NAME" },
     { "daemon", 'd', arg_flag, &daemonize, "daemonize", "daemonize" },
     { "daemon-child", 0, arg_flag, &daemon_child_fd, NULL, NULL }, /* priv */
     { "reverse-proxied", 0, arg_flag, &reverse_proxied_flag,
@@ -2076,6 +2420,67 @@ load_plugins(krb5_context context)
 #endif
 }
 
+static void
+get_csrf_prot_type(krb5_context context)
+{
+    char * const *strs = csrf_prot_type_strs.strings;
+    size_t n = csrf_prot_type_strs.num_strings;
+    size_t i;
+    char **freeme = NULL;
+
+    if (csrf_header == NULL)
+        csrf_header = krb5_config_get_string(context, NULL, "bx509d",
+                                             "csrf_protection_csrf_header",
+                                             NULL);
+
+    if (n == 0) {
+        char * const *p;
+
+        strs = freeme = krb5_config_get_strings(context, NULL, "bx509d",
+                                                "csrf_protection_type", NULL);
+        for (p = strs; p && p; p++)
+            n++;
+    }
+
+    for (i = 0; i < n; i++) {
+        if (strcmp(strs[i], "GET-with-header") == 0)
+            csrf_prot_type |= CSRF_PROT_GET_WITH_HEADER;
+        else if (strcmp(strs[i], "GET-with-token") == 0)
+            csrf_prot_type |= CSRF_PROT_GET_WITH_TOKEN;
+        else if (strcmp(strs[i], "POST-with-header") == 0)
+            csrf_prot_type |= CSRF_PROT_POST_WITH_HEADER;
+        else if (strcmp(strs[i], "POST-with-token") == 0)
+            csrf_prot_type |= CSRF_PROT_POST_WITH_TOKEN;
+    }
+    free(freeme);
+
+    /*
+     * For GETs we default to no CSRF protection as our GETable resources are
+     * safe and idempotent and we count on the browser not to make the
+     * responses available to cross-site requests.
+     *
+     * But, really, we don't want browsers even making these requests since, if
+     * the browsers behave correctly, then there's no point, and if they don't
+     * behave correctly then that could be catastrophic.  Of course, there's no
+     * guarantee that a browser won't have other catastrophic bugs, but still,
+     * we should probably change this default in the future:
+     *
+     *  if (!(csrf_prot_type & CSRF_PROT_GET_WITH_HEADER) &&
+     *      !(csrf_prot_type & CSRF_PROT_GET_WITH_TOKEN))
+     *      csrf_prot_type |= <whatever-the-new-default-should-be>;
+     */
+
+    /*
+     * For POSTs we default to CSRF protection with anti-CSRF tokens even
+     * though out POSTable resources are safe and idempotent when POSTed and we
+     * could count on the browser not to make the responses available to
+     * cross-site requests.
+     */
+    if (!(csrf_prot_type & CSRF_PROT_POST_WITH_HEADER) &&
+        !(csrf_prot_type & CSRF_PROT_POST_WITH_TOKEN))
+        csrf_prot_type |= CSRF_PROT_POST_WITH_TOKEN;
+}
+
 int
 main(int argc, char **argv)
 {
@@ -2136,6 +2541,8 @@ main(int argc, char **argv)
     if ((errno = get_krb5_context(&context)))
         err(1, "Could not init krb5 context (config file issue?)");
 
+    get_csrf_prot_type(context);
+
     if (!realm) {
         char *s;
 
@@ -2149,6 +2556,7 @@ main(int argc, char **argv)
                                  &kadm_handle)))
         err(1, "Could not connect to HDB");
     kadm5_destroy(kadm_handle);
+    kadm_handle = NULL;
 
     my_openlog(context, "httpkadmind", &logfac);
     load_plugins(context);
@@ -2273,11 +2681,18 @@ again:
         sin.sin_family = AF_INET;
         sin.sin_port = htons(port);
         current = MHD_start_daemon(flags, port,
+                                   /*
+                                    * This is a connection access callback.  We
+                                    * don't use it.
+                                    */
                                    NULL, NULL,
+                                   /* This is our request handler */
                                    route, (char *)NULL,
                                    MHD_OPTION_SOCK_ADDR, &sin,
                                    MHD_OPTION_CONNECTION_LIMIT, (unsigned int)200,
                                    MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int)10,
+                                   /* This is our request cleanup handler */
+                                   MHD_OPTION_NOTIFY_COMPLETED, cleanup_req, NULL,
                                    MHD_OPTION_END);
     } else if (sock != MHD_INVALID_SOCKET) {
         /*
@@ -2292,6 +2707,7 @@ again:
                                    MHD_OPTION_CONNECTION_LIMIT, (unsigned int)200,
                                    MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int)10,
                                    MHD_OPTION_LISTEN_SOCKET, sock,
+                                   MHD_OPTION_NOTIFY_COMPLETED, cleanup_req, NULL,
                                    MHD_OPTION_END);
         sock = MHD_INVALID_SOCKET;
     } else {
@@ -2302,6 +2718,7 @@ again:
                                    MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
                                    MHD_OPTION_CONNECTION_LIMIT, (unsigned int)200,
                                    MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int)10,
+                                   MHD_OPTION_NOTIFY_COMPLETED, cleanup_req, NULL,
                                    MHD_OPTION_END);
     }
     if (current == NULL)
index 98a405e17d9837066578aa3ade77ccb63df4a5c6..9142d68b24b0ac2d564a6be7da5d369468332db0 100644 (file)
@@ -189,7 +189,34 @@ _kdc_audit_trail(kdc_request_t r, krb5_error_code ret)
     CASE(KRB5KDC_ERR_SERVICE_NOTYET);
     CASE(KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN);
     CASE(KRB5KDC_ERR_TRTYPE_NOSUPP);
+    CASE(KRB5KRB_AP_ERR_BADADDR);
+    CASE(KRB5KRB_AP_ERR_BADDIRECTION);
+    CASE(KRB5KRB_AP_ERR_BAD_INTEGRITY);
+    CASE(KRB5KRB_AP_ERR_BADKEYVER);
+    CASE(KRB5KRB_AP_ERR_BADMATCH);
+    CASE(KRB5KRB_AP_ERR_BADORDER);
+    CASE(KRB5KRB_AP_ERR_BADSEQ);
+    CASE(KRB5KRB_AP_ERR_BADVERSION);
+    CASE(KRB5KRB_AP_ERR_ILL_CR_TKT);
+    CASE(KRB5KRB_AP_ERR_INAPP_CKSUM);
+    CASE(KRB5KRB_AP_ERR_METHOD);
+    CASE(KRB5KRB_AP_ERR_MODIFIED);
+    CASE(KRB5KRB_AP_ERR_MSG_TYPE);
+    CASE(KRB5KRB_AP_ERR_MUT_FAIL);
+    CASE(KRB5KRB_AP_ERR_NOKEY);
+    CASE(KRB5KRB_AP_ERR_NOT_US);
+    CASE(KRB5KRB_AP_ERR_REPEAT);
+    CASE(KRB5KRB_AP_ERR_SKEW);
+    CASE(KRB5KRB_AP_ERR_TKT_EXPIRED);
+    CASE(KRB5KRB_AP_ERR_TKT_INVALID);
+    CASE(KRB5KRB_AP_ERR_TKT_NYV);
+    CASE(KRB5KRB_AP_ERR_V4_REPLY);
+    CASE(KRB5KRB_AP_PATH_NOT_ACCEPTED);
+    CASE(KRB5KRB_AP_WRONG_PRINC);
+    CASE(KRB5KRB_ERR_FIELD_TOOLONG);
+    CASE(KRB5KRB_ERR_GENERIC);
     CASE(KRB5KRB_ERR_RESPONSE_TOO_BIG);
+
     case 0:
        retname = "SUCCESS";
        break;
index 2300eb53299db0fd61bc3bf9aa0203a3fbb51325..b46a8931ad35df38b4c761e745c380824dedf1b0 100644 (file)
  * <ext> is one of:
  *
  *  - pkinit        (SAN)
- *  - xmpt          (SAN)
- *  - emailt        (SAN)
- *  - ms-upt        (SAN)
- *  - dnsnamt       (SAN)
+ *  - xmpp          (SAN)
+ *  - email         (SAN)
+ *  - ms-upn        (SAN)
+ *  - dnsname       (SAN)
  *  - eku           (EKU OID)
  *
  * and <value> is a display form of the SAN or EKU OID, with SANs URL-encoded
index 91db2edcafa432f6a59de8fb065a1acc65c915c6..96ad36fd29e0349dd1af60ee1cf22098a444cd19 100644 (file)
@@ -47,6 +47,7 @@ heimtools_LDADD = \
        $(top_builddir)/lib/sl/libsl.la \
        $(kinit_LDADD) \
        $(LIB_readline) \
+        $(LIB_heimbase) \
        $(LIB_hx509)
 
 dist_heimtools_SOURCES = heimtools.c klist.c kx509.c kswitch.c copy_cred_cache.c
index 6ac4b45426a9927a45b6aff808fa53771d0ba4d1..01cb8ed1a41b59a9d3bba1496b6a3a72f41cc816 100644 (file)
@@ -1321,13 +1321,15 @@ renew_func(void *ptr)
        ret = get_new_tickets(ctx->context, ctx->principal, ctx->ccache,
                              ctx->ticket_life, 0, ctx->anonymous_pkinit);
     }
-    expire = ticket_lifetime(ctx->context, ctx->ccache, ctx->principal,
-                            server_str, &renew_expire);
+    if (ret == 0) {
+       expire = ticket_lifetime(ctx->context, ctx->ccache, ctx->principal,
+                                server_str, &renew_expire);
 
 #ifndef NO_AFS
-    if (ret == 0 && server_str == NULL && do_afslog && k_hasafs())
-       krb5_afslog(ctx->context, ctx->ccache, NULL, NULL);
+       if (server_str == NULL && do_afslog && k_hasafs())
+           krb5_afslog(ctx->context, ctx->ccache, NULL, NULL);
 #endif
+    }
 
     update_siginfo_msg(expire, server_str);
 
index b33c3c28a7aa33ec0623b0ca9ef787e218a2278a..f0a438218b78abde0f8101f3304c84cc258d8845 100644 (file)
@@ -38,7 +38,7 @@
 #include "heimtools-commands.h"
 #undef HC_DEPRECATED_CRYPTO
 
-static char*
+static const char *
 printable_time_internal(time_t t, int x)
 {
     static char s[128];
@@ -52,13 +52,13 @@ printable_time_internal(time_t t, int x)
     return s;
 }
 
-static char*
+static const char *
 printable_time(time_t t)
 {
     return printable_time_internal(t, 20);
 }
 
-static char*
+static const char *
 printable_time_long(time_t t)
 {
     return printable_time_internal(t, 20);
@@ -136,18 +136,173 @@ print_cred(krb5_context context, krb5_creds *cred, rtbl_t ct, int do_flags)
 }
 
 static void
-print_cred_verbose(krb5_context context, krb5_creds *cred, int do_json)
+cred2json(krb5_context context, krb5_creds *cred, heim_array_t tix)
 {
-    size_t j;
+    heim_dict_t t = heim_dict_create(10); /* ticket top-level */
+    heim_dict_t e = heim_dict_create(10); /* ticket times */
+    heim_dict_t f = heim_dict_create(20); /* flags */
+    heim_object_t o;
+    char buf[16], *sp = buf;
     char *str;
     krb5_error_code ret;
     krb5_timestamp sec;
 
-    if (do_json) { /* XXX support more json formating later */
-       printf("{ \"verbose-supported\" : false }");
-       return;
+    heim_array_append_value(tix, t);
+    krb5_timeofday(context, &sec);
+
+    /*
+     * JSON object names (keys) that start with capitals are for compatibility
+     * with the JSON we used to output.  The others are new.
+     */
+    heim_dict_set_value(t, HSTR("times"), e);
+    heim_dict_set_value(t, HSTR("flags"), f);
+
+    heim_dict_set_value(e, HSTR("authtime"),
+                        o = heim_number_create(cred->times.authtime));
+    heim_release(o);
+    heim_dict_set_value(t, HSTR("Issued"),
+                        o = heim_string_create(printable_time(cred->times.authtime)));
+    heim_release(o);
+        heim_dict_set_value(e, HSTR("starttime"), heim_null_create());
+    if (cred->times.starttime) {
+        heim_dict_set_value(e, HSTR("starttime"),
+                            o = heim_number_create(cred->times.starttime));
+        heim_release(o);
+        heim_dict_set_value(t, HSTR("Starttime"),
+                            o = heim_string_create(printable_time(cred->times.starttime)));
+        heim_release(o);
+    }
+
+    if (cred->times.renew_till) {
+        heim_dict_set_value(e, HSTR("renew_till"),
+                            o = heim_number_create(cred->times.starttime));
+        heim_release(o);
+        heim_dict_set_value(t, HSTR("Renew till"),
+                            o = heim_string_create(printable_time(cred->times.starttime)));
+        heim_release(o);
     }
 
+    heim_dict_set_value(e, HSTR("endtime"),
+                        o = heim_number_create(cred->times.endtime));
+    heim_release(o);
+
+    if (cred->times.endtime > sec) {
+        heim_dict_set_value(t, HSTR("Expires"),
+                            o = heim_string_create(printable_time(cred->times.endtime)));
+        heim_release(o);
+        heim_dict_set_value(t, HSTR("expired"), heim_bool_create(0));
+    } else {
+        heim_dict_set_value(t, HSTR("Expires"), HSTR(">>>Expired<<<"));
+        heim_dict_set_value(t, HSTR("expired"), heim_bool_create(1));
+    }
+
+    ret = krb5_unparse_name(context, cred->server, &str);
+    if (ret)
+       krb5_err(context, 1, ret, "krb5_unparse_name");
+    heim_dict_set_value(t, HSTR("Principal"), o = heim_string_create(str));
+    heim_release(o);
+
+    if (cred->flags.b.forwardable) {
+        heim_dict_set_value(f, HSTR("forwardable"), heim_bool_create(1));
+        *sp++ = 'F';
+    } else {
+        heim_dict_set_value(f, HSTR("forwardable"), heim_bool_create(0));
+    }
+    if (cred->flags.b.forwarded) {
+        heim_dict_set_value(f, HSTR("forwarded"), heim_bool_create(1));
+        *sp++ = 'f';
+    } else {
+        heim_dict_set_value(f, HSTR("forwarded"), heim_bool_create(0));
+    }
+    if (cred->flags.b.proxiable) {
+        heim_dict_set_value(f, HSTR("proxiable"), heim_bool_create(1));
+        *sp++ = 'P';
+    } else {
+        heim_dict_set_value(f, HSTR("proxiable"), heim_bool_create(0));
+    }
+    if (cred->flags.b.proxy) {
+        heim_dict_set_value(f, HSTR("proxy"), heim_bool_create(1));
+        *sp++ = 'p';
+    } else {
+        heim_dict_set_value(f, HSTR("proxy"), heim_bool_create(0));
+    }
+    if (cred->flags.b.may_postdate) {
+        heim_dict_set_value(f, HSTR("may_postdate"), heim_bool_create(1));
+        *sp++ = 'D';
+    } else {
+        heim_dict_set_value(f, HSTR("may_postdate"), heim_bool_create(0));
+    }
+    if (cred->flags.b.postdated) {
+        heim_dict_set_value(f, HSTR("postdated"), heim_bool_create(1));
+        *sp++ = 'd';
+    } else {
+        heim_dict_set_value(f, HSTR("postdated"), heim_bool_create(0));
+    }
+    if (cred->flags.b.renewable) {
+        heim_dict_set_value(f, HSTR("renewable"), heim_bool_create(1));
+        *sp++ = 'R';
+    } else {
+        heim_dict_set_value(f, HSTR("renewable"), heim_bool_create(0));
+    }
+    if (cred->flags.b.initial) {
+        heim_dict_set_value(f, HSTR("initial"), heim_bool_create(1));
+        *sp++ = 'I';
+    } else {
+        heim_dict_set_value(f, HSTR("initial"), heim_bool_create(0));
+    }
+    if (cred->flags.b.invalid) {
+        heim_dict_set_value(f, HSTR("invalid"), heim_bool_create(1));
+        *sp++ = 'i';
+    } else {
+        heim_dict_set_value(f, HSTR("invalid"), heim_bool_create(0));
+    }
+    if (cred->flags.b.pre_authent) {
+        heim_dict_set_value(f, HSTR("pre_authent"), heim_bool_create(1));
+        *sp++ = 'A';
+    } else {
+        heim_dict_set_value(f, HSTR("pre_authent"), heim_bool_create(0));
+    }
+    if (cred->flags.b.hw_authent) {
+        heim_dict_set_value(f, HSTR("hw_authent"), heim_bool_create(1));
+        *sp++ = 'H';
+    } else {
+        heim_dict_set_value(f, HSTR("hw_authent"), heim_bool_create(0));
+    }
+    if (cred->flags.b.transited_policy_checked) {
+        heim_dict_set_value(f, HSTR("transited_policy_checked"), heim_bool_create(1));
+        *sp++ = 'T';
+    } else {
+        heim_dict_set_value(f, HSTR("transited_policy_checked"), heim_bool_create(0));
+    }
+    if (cred->flags.b.ok_as_delegate) {
+        heim_dict_set_value(f, HSTR("ok_as_delegate"), heim_bool_create(1));
+        *sp++ = 'O';
+    } else {
+        heim_dict_set_value(f, HSTR("ok_as_delegate"), heim_bool_create(0));
+    }
+    if (cred->flags.b.anonymous) {
+        heim_dict_set_value(f, HSTR("anonymous"), heim_bool_create(1));
+        *sp++ = 'a';
+    } else {
+        heim_dict_set_value(f, HSTR("anonymous"), heim_bool_create(0));
+    }
+    *sp = '\0';
+    heim_dict_set_value(t, HSTR("Flags"), o = heim_string_create(sp));
+    heim_release(e);
+    heim_release(f);
+    heim_release(t);
+    heim_release(o);
+    free(str);
+}
+
+static void
+print_cred_verbose(krb5_context context, krb5_creds *cred)
+{
+    size_t j;
+    char *str;
+    krb5_error_code ret;
+    krb5_timestamp sec;
+
     krb5_timeofday (context, &sec);
 
     ret = krb5_unparse_name(context, cred->server, &str);
@@ -252,6 +407,74 @@ print_cred_verbose(krb5_context context, krb5_creds *cred, int do_json)
     printf("\n\n");
 }
 
+static void
+cache2json(krb5_context context,
+          krb5_ccache ccache,
+          krb5_principal principal,
+          heim_dict_t dict)
+{
+    heim_array_t tix = heim_array_create();
+    heim_object_t o;
+    char *str, *fullname;
+    char *name = NULL;
+    krb5_error_code ret;
+    krb5_cc_cursor cursor;
+    krb5_creds creds;
+    krb5_deltat sec;
+
+    ret = krb5_unparse_name(context, principal, &str);
+    if (ret)
+       krb5_err(context, 1, ret, "krb5_unparse_name");
+
+    ret = krb5_cc_get_full_name(context, ccache, &fullname);
+    if (ret)
+       krb5_err(context, 1, ret, "krb5_cc_get_full_name");
+
+    heim_dict_set_value(dict, HSTR("cache"),
+                        o = heim_string_create(fullname));
+    heim_release(o);
+    heim_dict_set_value(dict, HSTR("principal"),
+                        o = heim_string_create(str));
+    heim_release(o);
+    heim_dict_set_value(dict, HSTR("cache_version"),
+                        o = heim_number_create(krb5_cc_get_version(context,
+                                                                   ccache)));
+    heim_release(o);
+    free(str);
+       
+    ret = krb5_cc_get_friendly_name(context, ccache, &name);
+    if (ret == 0) {
+        heim_dict_set_value(dict, HSTR("friendly_name"),
+                            o = heim_string_create(name));
+        heim_release(o);
+    }
+    free(name);
+
+    ret = krb5_cc_get_kdc_offset(context, ccache, &sec);
+    if (ret == 0) {
+        heim_dict_set_value(dict, HSTR("kdc_offset"),
+                            o = heim_number_create(sec));
+        heim_release(o);
+    }
+
+    heim_dict_set_value(dict, HSTR("tickets"), tix);
+    ret = krb5_cc_start_seq_get(context, ccache, &cursor);
+    if (ret)
+       krb5_err(context, 1, ret, "krb5_cc_start_seq_get");
+
+    while ((ret = krb5_cc_next_cred(context, ccache, &cursor, &creds)) == 0) {
+        cred2json(context, &creds, tix);
+       krb5_free_cred_contents(context, &creds);
+    }
+    if (ret != KRB5_CC_END)
+       krb5_err(context, 1, ret, "krb5_cc_get_next");
+    ret = krb5_cc_end_seq_get(context, ccache, &cursor);
+    if (ret)
+       krb5_err(context, 1, ret, "krb5_cc_end_seq_get");
+    heim_release(tix);
+    free(fullname);
+}
+
 /*
  * Print all tickets in `ccache' on stdout, verbosely if do_verbose.
  */
@@ -262,8 +485,7 @@ print_tickets(krb5_context context,
              krb5_principal principal,
              int do_verbose,
              int do_flags,
-             int do_hidden,
-             int do_json)
+             int do_hidden)
 {
     char *str, *name, *fullname;
     krb5_error_code ret;
@@ -271,7 +493,6 @@ print_tickets(krb5_context context,
     krb5_creds creds;
     krb5_deltat sec;
     rtbl_t ct = NULL;
-    int print_comma = 0;
 
     ret = krb5_unparse_name (context, principal, &str);
     if (ret)
@@ -281,27 +502,26 @@ print_tickets(krb5_context context,
     if (ret)
        krb5_err (context, 1, ret, "krb5_cc_get_full_name");
 
-    if (!do_json) {
-       printf ("%17s: %s\n", N_("Credentials cache", ""), fullname);
-       printf ("%17s: %s\n", N_("Principal", ""), str);
-       
-       ret = krb5_cc_get_friendly_name(context, ccache, &name);
-       if (ret == 0) {
-           if (strcmp(name, str) != 0)
-               printf ("%17s: %s\n", N_("Friendly name", ""), name);
-           free(name);
-       }
+    printf ("%17s: %s\n", N_("Credentials cache", ""), fullname);
+    printf ("%17s: %s\n", N_("Principal", ""), str);
        
-       if(do_verbose) {
-           printf ("%17s: %d\n", N_("Cache version", ""),
-                   krb5_cc_get_version(context, ccache));
-       } else {
-           krb5_cc_set_flags(context, ccache, KRB5_TC_NOTICKET);
-       }
+    ret = krb5_cc_get_friendly_name(context, ccache, &name);
+    if (ret == 0) {
+        if (strcmp(name, str) != 0) {
+            printf ("%17s: %s\n", N_("Friendly name", ""), name);
+        }
+        free(name);
+    }
+
+    if(do_verbose) {
+        printf ("%17s: %d\n", N_("Cache version", ""),
+                krb5_cc_get_version(context, ccache));
+    }
        
-       ret = krb5_cc_get_kdc_offset(context, ccache, &sec);
+    ret = krb5_cc_get_kdc_offset(context, ccache, &sec);
+    if (ret == 0) {
+        if (do_verbose && sec != 0) {
        
-       if (ret == 0 && do_verbose && sec != 0) {
            char buf[BUFSIZ];
            int val;
            int sig;
@@ -317,18 +537,16 @@ print_tickets(krb5_context context,
 
            printf ("%17s: %s%s\n", N_("KDC time offset", ""),
                    sig == -1 ? "-" : "", buf);
-       }
-       printf("\n");
-    } else {
-       printf ("{ \"cache\" : \"%s\", \"principal\" : \"%s\", ", fullname, str);
+        }
     }
+    printf("\n");
     free(str);
 
     ret = krb5_cc_start_seq_get (context, ccache, &cursor);
     if (ret)
        krb5_err(context, 1, ret, "krb5_cc_start_seq_get");
 
-    if(!do_verbose) {
+    if (!do_verbose) {
        ct = rtbl_create();
        rtbl_add_column(ct, COL_ISSUED, 0);
        rtbl_add_column(ct, COL_EXPIRES, 0);
@@ -336,21 +554,12 @@ print_tickets(krb5_context context,
            rtbl_add_column(ct, COL_FLAGS, 0);
        rtbl_add_column(ct, COL_PRINCIPAL, 0);
        rtbl_set_separator(ct, "  ");
-       if (do_json) {
-           rtbl_set_flags(ct, RTBL_JSON);
-           printf("\"tickets\" : ");
-       }
     }
-    if (do_verbose && do_json)
-       printf("\"tickets\" : [");
     while ((ret = krb5_cc_next_cred(context, ccache, &cursor, &creds)) == 0) {
        if (!do_hidden && krb5_is_config_principal(context, creds.server)) {
            ;
        } else if (do_verbose) {
-            if (do_json && print_comma)
-                printf(",");
-           print_cred_verbose(context, &creds, do_json);
-            print_comma = 1;
+           print_cred_verbose(context, &creds);
        } else {
            print_cred(context, &creds, ct, do_flags);
        }
@@ -366,11 +575,6 @@ print_tickets(krb5_context context,
        rtbl_format(ct, stdout);
        rtbl_destroy(ct);
     }
-    if (do_json) {
-       if (do_verbose)
-           printf("]");
-       printf("}");
-    }
     free(fullname);
 }
 
@@ -475,10 +679,10 @@ display_tokens(int do_verbose)
  */
 
 static int
-display_v5_ccache (krb5_context context, krb5_ccache ccache,
-                  int do_test, int do_verbose,
-                  int do_flags, int do_hidden,
-                  int do_json)
+display_v5_ccache(krb5_context context, krb5_ccache ccache,
+                 int do_test, int do_verbose,
+                 int do_flags, int do_hidden,
+                 heim_dict_t dict)
 {
     krb5_error_code ret;
     krb5_principal principal;
@@ -487,10 +691,8 @@ display_v5_ccache (krb5_context context, krb5_ccache ccache,
 
     ret = krb5_cc_get_principal (context, ccache, &principal);
     if (ret) {
-       if (do_json) {
-           printf("{}");
+       if (dict)
            return 0;
-       }
        if(ret == ENOENT) {
            if (!do_test)
                krb5_warnx(context, N_("No ticket file: %s", ""),
@@ -499,11 +701,18 @@ display_v5_ccache (krb5_context context, krb5_ccache ccache,
        } else
            krb5_err (context, 1, ret, "krb5_cc_get_principal");
     }
-    if (do_test)
-       exit_status = check_expiration(context, ccache, NULL);
-    else
-       print_tickets (context, ccache, principal, do_verbose,
-                      do_flags, do_hidden, do_json);
+    exit_status = check_expiration(context, ccache, NULL);
+    if (!do_test) {
+        if (dict) {
+            heim_dict_set_value(dict, HSTR("expired"),
+                                heim_bool_create(!!exit_status));
+            cache2json(context, ccache, principal, dict);
+        } else {
+            print_tickets(context, ccache, principal, do_verbose,
+                          do_flags, do_hidden);
+        }
+        exit_status = 0;
+    }
 
     ret = krb5_cc_close (context, ccache);
     if (ret)
@@ -514,6 +723,87 @@ display_v5_ccache (krb5_context context, krb5_ccache ccache,
     return exit_status;
 }
 
+static int
+caches2json(krb5_context context)
+{
+    krb5_cccol_cursor cursor;
+    const char *cdef_name = krb5_cc_default_name(context);
+    char *def_name;
+    heim_object_t o;
+    heim_array_t a = heim_array_create();
+    krb5_error_code ret;
+    krb5_ccache id;
+
+    if ((def_name = krb5_cccol_get_default_ccname(context)) == NULL)
+        cdef_name = krb5_cc_default_name(context);
+    if (!def_name && cdef_name && (def_name = strdup(cdef_name)) == NULL)
+        krb5_err(context, 1, ENOMEM, "Out of memory");
+
+    ret = krb5_cccol_cursor_new(context, &cursor);
+    if (ret == KRB5_CC_NOSUPP) {
+        free(def_name);
+       return 0;
+    }
+    else if (ret)
+       krb5_err (context, 1, ret, "krb5_cc_cache_get_first");
+
+    while (krb5_cccol_cursor_next(context, cursor, &id) == 0 && id != NULL) {
+        heim_dict_t dict = heim_dict_create(10);
+       int expired = 0;
+       char *name;
+       time_t t;
+
+       expired = check_expiration(context, id, &t);
+       ret = krb5_cc_get_friendly_name(context, id, &name);
+       if (ret == 0) {
+           char *fname;
+
+            heim_dict_set_value(dict, HSTR("Name"),
+                                o = heim_string_create(name));
+            heim_release(o);
+           free(name);
+
+           if (expired)
+               o = heim_string_create(N_(">>> Expired <<<", ""));
+           else
+               o = heim_string_create(printable_time(t));
+            heim_dict_set_value(dict, HSTR("Expires"), o);
+            heim_release(o);
+
+           ret = krb5_cc_get_full_name(context, id, &fname);
+           if (ret)
+               krb5_err (context, 1, ret, "krb5_cc_get_full_name");
+
+            heim_dict_set_value(dict, HSTR("Cache Name"),
+                                o = heim_string_create(fname));
+            heim_release(o);
+
+           if (def_name && strcmp(fname, def_name) == 0)
+                heim_dict_set_value(dict, HSTR("is_default_cache"),
+                                    heim_bool_create(1));
+           else
+                heim_dict_set_value(dict, HSTR("is_default_cache"),
+                                    heim_bool_create(0));
+            heim_array_append_value(a, dict);
+            heim_release(dict);
+
+           krb5_xfree(fname);
+       }
+       krb5_cc_close(context, id);
+    }
+
+    krb5_cccol_cursor_free(context, &cursor);
+    free(def_name);
+
+    o = heim_json_copy_serialize(a, HEIM_JSON_F_STRICT | HEIM_JSON_F_INDENT2,
+                                 NULL);
+    printf("%s", heim_string_get_utf8(o));
+    heim_release(a);
+    heim_release(o);
+
+    return 0;
+}
+
 /*
  *
  */
@@ -550,8 +840,6 @@ list_caches(krb5_context context, struct klist_options *opt)
     rtbl_set_prefix(ct, "   ");
     rtbl_set_column_prefix(ct, COL_DEFCACHE, "");
     rtbl_set_column_prefix(ct, COL_NAME, " ");
-    if (opt->json_flag)
-       rtbl_set_flags(ct, RTBL_JSON);
 
     while (krb5_cccol_cursor_next(context, cursor, &id) == 0 && id != NULL) {
        int expired = 0;
@@ -579,9 +867,7 @@ list_caches(krb5_context context, struct klist_options *opt)
                krb5_err (context, 1, ret, "krb5_cc_get_full_name");
 
            rtbl_add_column_entry(ct, COL_CACHENAME, fname);
-           if (opt->json_flag)
-               ;
-           else if (def_name && strcmp(fname, def_name) == 0)
+           if (def_name && strcmp(fname, def_name) == 0)
                rtbl_add_column_entry(ct, COL_DEFCACHE, "*");
            else
                rtbl_add_column_entry(ct, COL_DEFCACHE, "");
@@ -597,9 +883,6 @@ list_caches(krb5_context context, struct klist_options *opt)
     rtbl_format(ct, stdout);
     rtbl_destroy(ct);
 
-    if (opt->json_flag)
-       printf("\n");
-
     return 0;
 }
 
@@ -611,6 +894,7 @@ int
 klist(struct klist_options *opt, int argc, char **argv)
 {
     krb5_error_code ret;
+    heim_object_t o = NULL;
     int exit_status = 0;
 
     int do_verbose =
@@ -627,7 +911,10 @@ klist(struct klist_options *opt, int argc, char **argv)
     }
 
     if (opt->list_all_flag) {
-       exit_status = list_caches(heimtools_context, opt);
+        if (opt->json_flag)
+            exit_status = caches2json(heimtools_context);
+        else
+            exit_status = list_caches(heimtools_context, opt);
        return exit_status;
     }
 
@@ -635,32 +922,28 @@ klist(struct klist_options *opt, int argc, char **argv)
        krb5_ccache id;
 
        if (opt->all_content_flag) {
+            heim_array_t a = opt->json_flag ? heim_array_create() : NULL;
            krb5_cc_cache_cursor cursor;
-           int first = 1;
 
            ret = krb5_cc_cache_get_first(heimtools_context, NULL, &cursor);
            if (ret)
                krb5_err(heimtools_context, 1, ret, "krb5_cc_cache_get_first");
 
-           if (opt->json_flag)
-               printf("[");
            while (krb5_cc_cache_next(heimtools_context, cursor, &id) == 0) {
-               if (opt->json_flag && !first)
-                   printf(",");
+                heim_dict_t dict = opt->json_flag ? heim_dict_create(10) : NULL;
 
                exit_status |= display_v5_ccache(heimtools_context, id, do_test,
                                                 do_verbose, opt->flags_flag,
                                                  opt->hidden_flag,
-                                                 opt->json_flag);
-               if (!opt->json_flag)
-                   printf("\n\n");
-
-               first = 0;
+                                                 dict);
+                if (a)
+                    heim_array_append_value(a, dict);
+                heim_release(dict);
            }
            krb5_cc_cache_end_seq_get(heimtools_context, cursor);
-           if (opt->json_flag)
-               printf("]");
+            o = a;
        } else {
+            heim_dict_t dict = opt->json_flag ? heim_dict_create(10) : NULL;
            if(opt->cache_string) {
                ret = krb5_cc_resolve(heimtools_context, opt->cache_string, &id);
                if (ret)
@@ -672,10 +955,25 @@ klist(struct klist_options *opt, int argc, char **argv)
            }
            exit_status = display_v5_ccache(heimtools_context, id, do_test,
                                            do_verbose, opt->flags_flag,
-                                            opt->hidden_flag, opt->json_flag);
+                                            opt->hidden_flag, dict);
+            o = dict;
        }
     }
 
+    if (o) {
+        heim_string_t s = heim_json_copy_serialize(o,
+                                                   HEIM_JSON_F_STRICT |
+                                                   HEIM_JSON_F_INDENT2,
+                                                   NULL);
+        
+        if (s == NULL)
+            errx(1, "Could not format JSON text");
+
+        printf("%s", heim_string_get_utf8(s));
+        heim_release(o);
+        heim_release(s);
+    }
+
     if (!do_test) {
 #ifndef NO_AFS
        if (opt->tokens_flag && k_hasafs()) {
index a7953df5fa17df10b32371eed598f0bb9a2a9dcb..9af27672c2a7bfbf74a71fdaeccc2a495cf2e762 100644 (file)
@@ -330,6 +330,11 @@ to form the names of the files generated.
 .It Fl Fl option-file=FILE
 Take additional command-line options from
 .Ar FILE .
+The options file must have one command-line option per-line, but
+leading whitespace is ignored, and lines that start with a hash
+symbol 
+.Sq ( # )
+are comments and are ignored.
 .It Fl Fl original-order
 Attempt to preserve the original order of type definition in the
 ASN.1 module.
index bec6f8b059f58785ea42fa2a9f56e795adf44af3..c6d420a2f16aaebd81de207e5d6a3de31cf8ba8a 100644 (file)
@@ -62,7 +62,7 @@ copy_type (const char *from, const char *to, const Type *t, int preserve)
            copy_primitive ("heim_integer", from, to);
            break;
        }
-        fallthrough;
+        HEIM_FALLTHROUGH;
     case TBoolean:
     case TEnumerated :
        fprintf(codefile, "*(%s) = *(%s);\n", to, from);
index 0507d54218031994fe1a0d26438553d2127c5b99..53b7bfe7b9059972779e7056d5f9aa8d5c525291 100644 (file)
@@ -56,7 +56,7 @@ free_type (const char *name, const Type *t, int preserve)
            free_primitive ("heim_integer", name);
            break;
        }
-        /* fallthrough; */
+        /* HEIM_FALLTHROUGH; */
     case TBoolean:
     case TEnumerated :
     case TNull:
index bcfdad62e2e75b9fcfa4dd42a239bb9429c91f59..569b4782b4d1f5cfa171dceff3602c3f10dbe961 100644 (file)
@@ -409,8 +409,16 @@ main(int argc, char **argv)
         sz = 2;
 
        while (fgets(buf, sizeof(buf), opt) != NULL) {
+            size_t buflen, ws;
+
            buf[strcspn(buf, "\n\r")] = '\0';
 
+            buflen = strlen(buf);
+            if ((ws = strspn(buf, " \t")))
+                memmove(buf, buf + ws, buflen -= ws);
+            if (buf[0] == '\0' || buf[0] == '#')
+                continue;
+
             if (len + 1 >= sz) {
                 arg = realloc(arg, (sz + (sz>>1) + 2) * sizeof(arg[0]));
                 if (arg == NULL) {
index 7a19e7477e3db5415480747819bff2400374ebd6..31eb66004ec496d292278e9702a3f202adb242f1 100644 (file)
@@ -2549,7 +2549,9 @@ _asn1_print(const struct asn1_template *t,
         }
         default: break;
         }
-        if (nnames)
+        if (nnames &&
+            (t->tt & A1_OP_MASK) != A1_OP_TYPE_DECORATE_EXTERN &&
+            (t->tt & A1_OP_MASK) != A1_OP_TYPE_DECORATE)
             r = rk_strpoolprintf(r, ",%s\"%s\":",
                                  indents ? indents : "",
                                  (const char *)(tnames++)->ptr);
index 3706fc8710d2f1b3a7b2a3fe9dac0ce3e096f89c..5d5c4e3b75e54c868e953263bdb75f1b8c11e148 100644 (file)
@@ -134,6 +134,28 @@ typedef struct heim_config_binding heim_config_section;
  * CF-like, JSON APIs
  */
 
+typedef enum heim_tid_enum {
+    HEIM_TID_NUMBER = 0,
+    HEIM_TID_NULL = 1,
+    HEIM_TID_BOOL = 2,
+    HEIM_TID_TAGGED_UNUSED2 = 3, /* reserved for tagged object types */
+    HEIM_TID_TAGGED_UNUSED3 = 4, /* reserved for tagged object types */
+    HEIM_TID_TAGGED_UNUSED4 = 5, /* reserved for tagged object types */
+    HEIM_TID_TAGGED_UNUSED5 = 6, /* reserved for tagged object types */
+    HEIM_TID_TAGGED_UNUSED6 = 7, /* reserved for tagged object types */
+    HEIM_TID_MEMORY = 128,
+    HEIM_TID_ARRAY = 129,
+    HEIM_TID_DICT = 130,
+    HEIM_TID_STRING = 131,
+    HEIM_TID_AUTORELEASE = 132,
+    HEIM_TID_ERROR = 133,
+    HEIM_TID_DATA = 134,
+    HEIM_TID_DB = 135,
+    HEIM_TID_PA_AUTH_MECH = 136,
+    HEIM_TID_PAC = 137,
+    HEIM_TID_USER = 255
+} heim_tid;
+
 typedef void * heim_object_t;
 typedef unsigned int heim_tid_t;
 typedef heim_object_t heim_bool_t;
@@ -463,7 +485,13 @@ typedef enum heim_json_flags {
        HEIM_JSON_F_STRICT = 31,
        HEIM_JSON_F_CNULL2JSNULL = 32,
        HEIM_JSON_F_TRY_DECODE_DATA = 64,
-       HEIM_JSON_F_ONE_LINE = 128
+       HEIM_JSON_F_ONE_LINE = 128,
+        HEIM_JSON_F_ESCAPE_NON_ASCII = 256,
+        HEIM_JSON_F_NO_ESCAPE_NON_ASCII = 512,
+        /* The default is to indent with one tab */
+       HEIM_JSON_F_INDENT2 = 1024,
+       HEIM_JSON_F_INDENT4 = 2048,
+       HEIM_JSON_F_INDENT8 = 4096,
 } heim_json_flags_t;
 
 heim_object_t heim_json_create(const char *, size_t, heim_json_flags_t,
index b9f63e56b6a136b768dbcb6c65654c9750a079ab..45ffb12d70e78394df3b0f8b18db50db722b5626 100644 (file)
@@ -47,29 +47,6 @@ typedef heim_string_t (*heim_type_description)(void *);
 
 typedef struct heim_type_data *heim_type_t;
 
-enum {
-    HEIM_TID_NUMBER = 0,
-    HEIM_TID_NULL = 1,
-    HEIM_TID_BOOL = 2,
-    HEIM_TID_TAGGED_UNUSED2 = 3, /* reserved for tagged object types */
-    HEIM_TID_TAGGED_UNUSED3 = 4, /* reserved for tagged object types */
-    HEIM_TID_TAGGED_UNUSED4 = 5, /* reserved for tagged object types */
-    HEIM_TID_TAGGED_UNUSED5 = 6, /* reserved for tagged object types */
-    HEIM_TID_TAGGED_UNUSED6 = 7, /* reserved for tagged object types */
-    HEIM_TID_MEMORY = 128,
-    HEIM_TID_ARRAY = 129,
-    HEIM_TID_DICT = 130,
-    HEIM_TID_STRING = 131,
-    HEIM_TID_AUTORELEASE = 132,
-    HEIM_TID_ERROR = 133,
-    HEIM_TID_DATA = 134,
-    HEIM_TID_DB = 135,
-    HEIM_TID_PA_AUTH_MECH = 136,
-    HEIM_TID_PAC = 137,
-    HEIM_TID_USER = 255
-
-};
-
 struct heim_type_data {
     heim_tid_t tid;
     const char *name;
index 2ef371b975ea99f47b78d8e8ee238271fef71bfa..c7ef06588508359ac35794afd48efeb9fd0ebccb 100644 (file)
 #include <ctype.h>
 #include <base64.h>
 
+#ifndef WIN32
+#include <langinfo.h>
+#endif
+
 static heim_base_once_t heim_json_once = HEIM_BASE_ONCE_INIT;
 static heim_string_t heim_tid_data_uuid_key = NULL;
-static const char base64_chars[] =
-    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
 
 static void
 json_init_once(void *arg)
@@ -66,7 +68,7 @@ struct heim_strbuf {
 };
 
 static int
-base2json(heim_object_t, struct twojson *);
+base2json(heim_object_t, struct twojson *, int);
 
 static void
 indent(struct twojson *j)
@@ -74,8 +76,18 @@ indent(struct twojson *j)
     size_t i = j->indent;
     if (j->flags & HEIM_JSON_F_ONE_LINE)
        return;
-    while (i--)
-       j->out(j->ctx, "\t");
+    if (j->flags & HEIM_JSON_F_INDENT2)
+        while (i--)
+            j->out(j->ctx, "  ");
+    else if (j->flags & HEIM_JSON_F_INDENT4)
+        while (i--)
+            j->out(j->ctx, "    ");
+    else if (j->flags & HEIM_JSON_F_INDENT8)
+        while (i--)
+            j->out(j->ctx, "        ");
+    else
+        while (i--)
+            j->out(j->ctx, "\t");
 }
 
 static void
@@ -90,7 +102,7 @@ array2json(heim_object_t value, void *ctx, int *stop)
        j->out(j->ctx, NULL); /* eat previous '\n' if possible */
        j->out(j->ctx, ",\n");
     }
-    j->ret = base2json(value, j);
+    j->ret = base2json(value, j, 0);
 }
 
 static void
@@ -105,19 +117,77 @@ dict2json(heim_object_t key, heim_object_t value, void *ctx)
        j->out(j->ctx, NULL); /* eat previous '\n' if possible */
        j->out(j->ctx, ",\n");
     }
-    j->ret = base2json(key, j);
-    if (j->ret)
-       return;
-    j->out(j->ctx, " : \n");
-    j->indent++;
-    j->ret = base2json(value, j);
+    j->ret = base2json(key, j, 0);
     if (j->ret)
        return;
-    j->indent--;
+    switch (heim_get_tid(value)) {
+    case HEIM_TID_ARRAY:
+    case HEIM_TID_DICT:
+    case HEIM_TID_DATA:
+        j->out(j->ctx, ":\n");
+        j->indent++;
+        j->ret = base2json(value, j, 0);
+        if (j->ret)
+            return;
+        j->indent--;
+        break;
+    default:
+        j->out(j->ctx, ": ");
+        j->ret = base2json(value, j, 1);
+        break;
+    }
+}
+
+#ifndef WIN32
+static void
+init_is_utf8(void *ptr)
+{
+    *(int *)ptr = strcasecmp("utf-8", nl_langinfo(CODESET)) == 0;
+}
+#endif
+
+int
+heim_locale_is_utf8(void)
+{
+#ifdef WIN32
+    return 0; /* XXX Implement */
+#else
+    static int locale_is_utf8 = -1;
+    static heim_base_once_t once = HEIM_BASE_ONCE_INIT;
+
+    heim_base_once_f(&once, &locale_is_utf8, init_is_utf8);
+    return locale_is_utf8;
+#endif
+}
+
+static void
+out_escaped_bmp(struct twojson *j, const unsigned char *p, int nbytes)
+{
+    unsigned char e[sizeof("\\u0000")];
+    unsigned codepoint;
+
+    if (nbytes == 2)
+        codepoint = ((p[0] & 0x1f) << 6) | (p[1] & 0x3f);
+    else if (nbytes == 3)
+        codepoint = ((p[0] & 0x0f) << 12) | ((p[1] & 0x3f) << 6) | (p[2] & 0x3f);
+    else
+        abort();
+    e[0]  = '\\';
+    e[1]  = 'u';
+    e[2]  = codepoint >> 12;
+    e[2] += (e[2] < 10) ? '0' : ('A' - 10);
+    e[3]  = (codepoint >> 8) & 0x0f;
+    e[3] += (e[3] < 10) ? '0' : ('A' - 10);
+    e[4]  = (codepoint >> 4) & 0x0f;
+    e[4] += (e[4] < 10) ? '0' : ('A' - 10);
+    e[5]  =  codepoint       & 0x0f;
+    e[5] += (e[5] < 10) ? '0' : ('A' - 10);
+    e[6]  = '\0';
+    j->out(j->ctx, (char *)e);
 }
 
 static int
-base2json(heim_object_t obj, struct twojson *j)
+base2json(heim_object_t obj, struct twojson *j, int skip_indent)
 {
     heim_tid_t type;
     int first = 0;
@@ -166,12 +236,186 @@ base2json(heim_object_t obj, struct twojson *j)
        j->first = first;
        break;
 
-    case HEIM_TID_STRING:
-       indent(j);
+    case HEIM_TID_STRING: {
+       const unsigned char *s = (const unsigned char *)heim_string_get_utf8(obj);
+       const unsigned char *p;
+        unsigned int c, cp, ctop, cbot;
+        char e[sizeof("\\u0123\\u3210")];
+        int good;
+        size_t i;
+
+        if (!skip_indent)
+            indent(j);
        j->out(j->ctx, "\"");
-       j->out(j->ctx, heim_string_get_utf8(obj));
+        for (p = s; (c = *p); p++) {
+            switch (c) {
+            /* ASCII control characters w/ C-like escapes */
+            case '\b': j->out(j->ctx, "\\b");  continue;
+            case '\f': j->out(j->ctx, "\\f");  continue;
+            case '\n': j->out(j->ctx, "\\n");  continue;
+            case '\r': j->out(j->ctx, "\\r");  continue;
+            case '\t': j->out(j->ctx, "\\t");  continue;
+            /* Other must-escape non-control ASCII characters */
+            case '"':  j->out(j->ctx, "\\\""); continue;
+            case '\\': j->out(j->ctx, "\\\\"); continue;
+            default: break;
+            }
+
+            /*
+             * JSON string encoding is... complex.
+             *
+             * Invalid UTF-8 w/  HEIM_JSON_F_STRICT_STRINGS set -> return 1
+             *
+             * Invalid UTF-8 w/o HEIM_JSON_F_STRICT_STRINGS set -> pass
+             * through, a sort of Heimdal WTF-8, but not _the_ WTF-8.
+             */
+            if (c < 0x20) {
+                /* ASCII control character w/o C-like escape */
+                e[0] = '\\';
+                e[1] = 'u';
+                e[2] = '0';
+                e[3] = '0';
+                e[4] = "0123456789ABCDEF"[c>>4];
+                e[5] = "0123456789ABCDEF"[c & 0x0f];
+                e[6] = '\0';
+                j->out(j->ctx, e);
+                continue;
+            }
+            if (c < 0x80) {
+                /* ASCII */
+                e[0] = c;
+                e[1] = '\0';
+                j->out(j->ctx, e);
+                continue;
+            }
+            if ((c & 0xc0) == 0x80) {
+                /* UTF-8 bare non-leading byte */
+                if (!(j->flags & HEIM_JSON_F_STRICT_STRINGS)) {
+                    e[0] = c;
+                    e[1] = '\0';
+                    j->out(j->ctx, e);
+                    continue;
+                }
+                return 1;
+            }
+            if ((c & 0xe0) == 0xc0) {
+                /* UTF-8 leading byte of two-byte sequence */
+                good = 1;
+                for (i = 1; i < 2 && good && p[i]; i++) {
+                    if ((p[i] & 0xc0) != 0x80)
+                        good = 0;
+                }
+                if (i != 2)
+                    good = 0;
+                if (!good && !(j->flags & HEIM_JSON_F_STRICT_STRINGS)) {
+                    e[0] = c;
+                    e[1] = '\0';
+                    j->out(j->ctx, e);
+                    continue;
+                } else if (!good) {
+                    return 1;
+                }
+                if (j->flags & HEIM_JSON_F_ESCAPE_NON_ASCII) {
+                    out_escaped_bmp(j, p, 2);
+                    p += 1;
+                    continue;
+                }
+                e[0] = c;
+                e[1] = p[1];
+                e[2] = '\0';
+                j->out(j->ctx, e);
+                p += 1;
+                continue;
+            }
+            if ((c & 0xf0) == 0xe0) {
+                /* UTF-8 leading byte of three-byte sequence */
+                good = 1;
+                for (i = 1; i < 3 && good && p[i]; i++) {
+                    if ((p[i] & 0xc0) != 0x80)
+                        good = 0;
+                }
+                if (i != 3)
+                    good = 0;
+                if (!good && !(j->flags & HEIM_JSON_F_STRICT_STRINGS)) {
+                    e[0] = c;
+                    e[1] = '\0';
+                    j->out(j->ctx, e);
+                    continue;
+                } else if (!good) {
+                    return 1;
+                }
+                if (j->flags & HEIM_JSON_F_ESCAPE_NON_ASCII) {
+                    out_escaped_bmp(j, p, 3);
+                    p += 2;
+                    continue;
+                }
+                e[0] = c;
+                e[1] = p[1];
+                e[2] = p[2];
+                e[3] = '\0';
+                j->out(j->ctx, e);
+                p += 2;
+                continue;
+            }
+
+            if (c > 0xf7) {
+                /* Invalid UTF-8 leading byte */
+                if (!(j->flags & HEIM_JSON_F_STRICT_STRINGS)) {
+                    e[0] = c;
+                    e[1] = '\0';
+                    j->out(j->ctx, e);
+                    continue;
+                }
+                return 1;
+            }
+
+            /*
+             * A codepoint > U+FFFF, needs encoding a la UTF-16 surrogate
+             * pair because JSON takes after JS which uses UTF-16.  Ugly.
+             */
+            cp = c & 0x7;
+            good = 1;
+            for (i = 1; i < 4 && good && p[i]; i++) {
+                if ((p[i] & 0xc0) == 0x80)
+                    cp = (cp << 6) | (p[i] & 0x3f);
+                else
+                    good = 0;
+            }
+            if (i != 4)
+                good = 0;
+            if (!good && !(j->flags & HEIM_JSON_F_STRICT_STRINGS)) {
+                e[0] = c;
+                e[1] = '\0';
+                j->out(j->ctx, e);
+                continue;
+            } else if (!good) {
+                return 1;
+            }
+            p += 3;
+
+            cp -= 0x10000;
+            ctop = 0xD800 + (cp >>   10);
+            cbot = 0xDC00 + (cp & 0x3ff);
+
+            e[0 ] = '\\';
+            e[1 ] = 'u';
+            e[2 ] = "0123456789ABCDEF"[(ctop         ) >> 12];
+            e[3 ] = "0123456789ABCDEF"[(ctop & 0x0f00) >>  8];
+            e[4 ] = "0123456789ABCDEF"[(ctop & 0x00f0) >>  4];
+            e[5 ] = "0123456789ABCDEF"[(ctop & 0x000f)      ];
+            e[6 ] = '\\';
+            e[7 ] = 'u';
+            e[8 ] = "0123456789ABCDEF"[(cbot         ) >> 12];
+            e[9 ] = "0123456789ABCDEF"[(cbot & 0x0f00) >>  8];
+            e[10] = "0123456789ABCDEF"[(cbot & 0x00f0) >>  4];
+            e[11] = "0123456789ABCDEF"[(cbot & 0x000f)      ];
+            e[12] = '\0';
+            j->out(j->ctx, e);
+            continue;
+        }
        j->out(j->ctx, "\"");
        break;
+    }
 
     case HEIM_TID_DATA: {
        heim_dict_t d;
@@ -220,7 +464,7 @@ base2json(heim_object_t obj, struct twojson *j)
                heim_release(d);
                return ENOMEM;
            }
-           ret = base2json(d, j);
+           ret = base2json(d, j, 0);
            heim_release(d);
            if (ret)
                return ret;
@@ -230,17 +474,20 @@ base2json(heim_object_t obj, struct twojson *j)
 
     case HEIM_TID_NUMBER: {
        char num[32];
-       indent(j);
+        if (!skip_indent)
+            indent(j);
        snprintf(num, sizeof (num), "%d", heim_number_get_int(obj));
        j->out(j->ctx, num);
        break;
     }
     case HEIM_TID_NULL:
-       indent(j);
+        if (!skip_indent)
+            indent(j);
        j->out(j->ctx, "null");
        break;
     case HEIM_TID_BOOL:
-       indent(j);
+        if (!skip_indent)
+            indent(j);
        j->out(j->ctx, heim_bool_val(obj) ? "true" : "false");
        break;
     default:
@@ -255,9 +502,6 @@ heim_base2json(heim_object_t obj, void *ctx, heim_json_flags_t flags,
 {
     struct twojson j;
 
-    if (flags & HEIM_JSON_F_STRICT_STRINGS)
-       return ENOTSUP; /* Sorry, not yet! */
-
     heim_base_once_f(&heim_json_once, NULL, json_init_once);
 
     j.indent = 0;
@@ -267,7 +511,11 @@ heim_base2json(heim_object_t obj, void *ctx, heim_json_flags_t flags,
     j.ret = 0;
     j.first = 1;
 
-    return base2json(obj, &j);
+    if (!(flags & HEIM_JSON_F_NO_ESCAPE_NON_ASCII) &&
+        !heim_locale_is_utf8())
+        j.flags |= HEIM_JSON_F_ESCAPE_NON_ASCII;
+
+    return base2json(obj, &j, 0);
 }
 
 
@@ -342,93 +590,425 @@ parse_number(struct parse_ctx *ctx)
     return heim_number_create(number * neg);
 }
 
+/*
+ * Read 4 hex digits from ctx->p.
+ *
+ * If we don't have enough, rewind ctx->p and return -1 .
+ */
+static int
+unescape_unicode(struct parse_ctx *ctx)
+{
+    int c = 0;
+    int i;
+
+    for (i = 0; i < 4 && ctx->p < ctx->pend; i++, ctx->p++) {
+        if (*ctx->p >= '0' && *ctx->p <= '9') {
+            c = (c << 4) + (*ctx->p - '0');
+        } else if (*ctx->p >= 'A' && *ctx->p <= 'F') {
+            c = (c << 4) + (10 + *ctx->p - 'A');
+        } else if (*ctx->p >= 'a' && *ctx->p <= 'f') {
+            c = (c << 4) + (10 + *ctx->p - 'a');
+        } else {
+            ctx->p -= i;
+            return -1;
+        }
+    }
+    return c;
+}
+
+static int
+encode_utf8(struct parse_ctx *ctx, char **pp, char *pend, int c)
+{
+    char *p = *pp;
+
+    if (c < 0x80) {
+        /* ASCII */
+        if (p >= pend) return 0;
+        *(p++) = c;
+        *pp = p;
+        return 1;
+    }
+    if (c < 0x800) {
+        /* 2 code unit UTF-8 sequence */
+        if (p >= pend) return 0;
+        *(p++) = 0xc0 | ((c >>  6)       );
+        if (p == pend) return 0;
+        *(p++) = 0x80 | ((c      ) & 0x3f);
+        *pp = p;
+        return 1;
+    }
+    if (c < 0x10000) {
+        /* 3 code unit UTF-8 sequence */
+        if (p >= pend) return 0;
+        *(p++) = 0xe0 | ((c >> 12)       );
+        if (p == pend) return 0;
+        *(p++) = 0x80 | ((c >>  6) & 0x3f);
+        if (p == pend) return 0;
+        *(p++) = 0x80 | ((c)       & 0x3f);
+        *pp = p;
+        return 1;
+    }
+    if (c < 0x110000) {
+        /* 4 code unit UTF-8 sequence */
+        if (p >= pend) return 0;
+        *(p++) = 0xf0 | ((c >> 18)       );
+        if (p == pend) return 0;
+        *(p++) = 0x80 | ((c >> 12) & 0x3f);
+        if (p == pend) return 0;
+        *(p++) = 0x80 | ((c >>  6) & 0x3f);
+        if (p == pend) return 0;
+        *(p++) = 0x80 | ((c)       & 0x3f);
+        *pp = p;
+        return 1;
+    }
+    return 0;
+}
+
+static heim_string_t
+parse_string_error(struct parse_ctx *ctx,
+                   char *freeme,
+                   const char *msg)
+{
+    free(freeme);
+    ctx->error = heim_error_create(EINVAL, "%s at %lu", msg, ctx->lineno);
+    return NULL;
+}
+
 static heim_string_t
 parse_string(struct parse_ctx *ctx)
 {
     const uint8_t *start;
-    int quote = 0;
-
-    if (ctx->flags & HEIM_JSON_F_STRICT_STRINGS) {
-       ctx->error = heim_error_create(EINVAL, "Strict JSON string encoding "
-                                      "not yet supported");
-       return NULL;
+    heim_object_t o;
+    size_t alloc_len = 0;
+    size_t need = 0;
+    char *p0, *p, *pend;
+    int strict = ctx->flags & HEIM_JSON_F_STRICT_STRINGS;
+    int binary = 0;
+
+    if (*ctx->p != '"')
+        return parse_string_error(ctx, NULL,
+                                  "Expected a JSON string but found "
+                                  "something else");
+    start = ++(ctx->p);
+
+    /* Estimate how many bytes we need to allocate */
+    p0 = p = pend = NULL;
+    for (need = 1; ctx->p < ctx->pend; ctx->p++) {
+        need++;
+        if (*ctx->p == '\\')
+            ctx->p++;
+        else if (*ctx->p == '"')
+            break;
     }
+    if (ctx->p == ctx->pend)
+        return parse_string_error(ctx, NULL, "Unterminated JSON string");
 
-    if (*ctx->p != '"') {
-       ctx->error = heim_error_create(EINVAL, "Expected a JSON string but "
-                                      "found something else at line %lu",
-                                      ctx->lineno);
-       return NULL;
+    ctx->p = start;
+    while (ctx->p < ctx->pend) {
+        const unsigned char *p_save;
+        int32_t ctop, cbot;
+
+        if (*ctx->p == '"') {
+            ctx->p++;
+            break;
+        }
+
+        /* Allocate or resize our output buffer if need be */
+        if (need || p == pend) {
+            char *tmp;
+
+            /*
+             * Work out how far p is into p0 to re-esablish p after
+             * the realloc()
+             */
+            size_t p0_to_p_len = (p - p0);
+
+            tmp = realloc(p0, alloc_len + need + 5 /* slop? */);
+
+            if (tmp == NULL) {
+                ctx->error = heim_error_create_enomem();
+                free(p0);
+                return NULL;
+            }
+            alloc_len += need + 5;
+
+            /*
+             * We have two pointers, p and p0, we want to keep them
+             * pointing into the same memory after the realloc()
+             */
+            p = tmp + p0_to_p_len;
+            p0 = tmp;
+            pend = p0 + alloc_len;
+
+            need = 0;
+        }
+
+        if (*ctx->p != '\\') {
+            unsigned char c = *ctx->p;
+
+            /*
+             * Not backslashed -> consume now.
+             *
+             * NOTE: All cases in this block must continue or return w/ error.
+             */
+
+            /* Check for unescaped ASCII control characters */
+            if (c == '\n') {
+                if (strict)
+                    return parse_string_error(ctx, p0,
+                                              "Unescaped newline in JSON string");
+                /* Count the newline but don't add it to the decoding */
+                ctx->lineno++;
+            } else if (strict && *ctx->p <= 0x1f) {
+                return parse_string_error(ctx, p0, "Unescaped ASCII control character");
+            } else if (c == 0) {
+                binary = 1;
+            }
+            if (!strict || c < 0x80) {
+                /* ASCII, or not strict -> no need to validate */
+                *(p++) = c;
+                ctx->p++;
+                continue;
+            }
+
+            /*
+             * Being strict for parsing means we want to detect malformed UTF-8
+             * sequences.
+             *
+             * If not strict then we just go on below and add to `p' whatever
+             * bytes we find in `ctx->p' as we find them.
+             *
+             * For each two-byte sequence we need one more byte in `p[]'.  For
+             * each three-byte sequence we need two more bytes in `p[]'.
+             *
+             * Setting `need' and looping will cause `p0' to be grown.
+             *
+             * NOTE: All cases in this block must continue or return w/ error.
+             */
+            if ((c & 0xe0) == 0xc0) {
+                /* Two-byte UTF-8 encoding */
+                if (pend - p < 2) {
+                    need = 2;
+                    continue; /* realloc p0 */
+                }
+
+                *(p++) = c;
+                ctx->p++;
+                if (ctx->p == ctx->pend)
+                    return parse_string_error(ctx, p0, "Truncated UTF-8");
+                c = *(ctx->p++);
+                if ((c & 0xc0) != 0x80)
+                    return parse_string_error(ctx, p0, "Truncated UTF-8");
+                *(p++) = c;
+                continue;
+            }
+            if ((c & 0xf0) == 0xe0) {
+                /* Three-byte UTF-8 encoding */
+                if (pend - p < 3) {
+                    need = 3;
+                    continue; /* realloc p0 */
+                }
+
+                *(p++) = c;
+                ctx->p++;
+                if (ctx->p == ctx->pend)
+                    return parse_string_error(ctx, p0, "Truncated UTF-8");
+                c = *(ctx->p++);
+                if ((c & 0xc0) != 0x80)
+                    return parse_string_error(ctx, p0, "Truncated UTF-8");
+                *(p++) = c;
+                c = *(ctx->p++);
+                if ((c & 0xc0) != 0x80)
+                    return parse_string_error(ctx, p0, "Truncated UTF-8");
+                *(p++) = c;
+                continue;
+            }
+            if ((c & 0xf8) == 0xf0)
+                return parse_string_error(ctx, p0, "UTF-8 sequence not "
+                                          "encoded as escaped UTF-16");
+            if ((c & 0xc0) == 0x80)
+                return parse_string_error(ctx, p0,
+                                          "Invalid UTF-8 "
+                                          "(bare continuation code unit)");
+
+            return parse_string_error(ctx, p0, "Not UTF-8");
+        }
+
+        /* Backslash-quoted character */
+        ctx->p++;
+        if (ctx->p == ctx->pend) {
+            ctx->error =
+                heim_error_create(EINVAL,
+                                  "Unterminated JSON string at line %lu",
+                                  ctx->lineno);
+            free(p0);
+            return NULL;
+        }
+        switch (*ctx->p) {
+        /* Simple escapes */
+        case  'b': *(p++) = '\b'; ctx->p++; continue;
+        case  'f': *(p++) = '\f'; ctx->p++; continue;
+        case  'n': *(p++) = '\n'; ctx->p++; continue;
+        case  'r': *(p++) = '\r'; ctx->p++; continue;
+        case  't': *(p++) = '\t'; ctx->p++; continue;
+        case  '"': *(p++) = '"';  ctx->p++; continue;
+        case '\\': *(p++) = '\\'; ctx->p++; continue;
+        /* Escaped Unicode handled below */
+        case  'u':
+            /*
+             * Worst case for !strict we need 11 bytes for a truncated non-BMP
+             * codepoint escape.  Call it 12.
+             */
+            if (strict)
+                need = 4;
+            else
+                need = 12;
+            if (pend - p < need) {
+                /* Go back to the backslash, realloc, try again */
+                ctx->p--;
+                continue;
+            }
+
+            need = 0;
+            ctx->p++;
+            break;
+        default:
+            if (!strict) {
+                *(p++) = *ctx->p;
+                ctx->p++;
+                continue;
+            }
+            ctx->error =
+                heim_error_create(EINVAL,
+                                  "Invalid backslash escape at line %lu",
+                                  ctx->lineno);
+            free(p0);
+            return NULL;
+        }
+
+        /* Unicode code point */
+        if (pend - p < 12) {
+            need = 12;
+            ctx->p -= 2; /* for "\\u" */
+            continue; /* This will cause p0 to be realloc'ed */
+        }
+        p_save = ctx->p;
+        ctop = cbot = -3;
+        ctop = unescape_unicode(ctx);
+        if (ctop == -1 && strict)
+            return parse_string_error(ctx, p0, "Invalid escaped Unicode");
+        if (ctop == -1) {
+            /*
+             * Not strict; tolerate bad input.
+             *
+             * Output "\\u" and then loop to treat what we expected to be four
+             * digits as if they were not part of an escaped Unicode codepoint.
+             */
+            ctx->p = p_save;
+            if (p < pend)
+                *(p++) = '\\';
+            if (p < pend)
+                *(p++) = 'u';
+            continue;
+        }
+        if (ctop == 0) {
+            *(p++) = '\0';
+            binary = 1;
+            continue;
+        }
+        if (ctop < 0xd800) {
+            if (!encode_utf8(ctx, &p, pend, ctop))
+                return parse_string_error(ctx, p0,
+                                          "Internal JSON string parse error");
+            continue;
+        }
+
+        /*
+         * We parsed the top escaped codepoint of a surrogate pair encoding
+         * of a non-BMP Unicode codepoint.  What follows must be another
+         * escaped codepoint.
+         */
+        if (ctx->p < ctx->pend && ctx->p[0] == '\\')
+            ctx->p++;
+        else
+            ctop = -1;
+        if (ctop > -1 && ctx->p < ctx->pend && ctx->p[0] == 'u')
+            ctx->p++;
+        else
+            ctop = -1;
+        if (ctop > -1) {
+            /* Parse the hex digits of the bottom half of the surrogate pair */
+            cbot = unescape_unicode(ctx);
+            if (cbot == -1 || cbot < 0xdc00)
+                ctop = -1;
+        }
+        if (ctop == -1) {
+            if (strict)
+                return parse_string_error(ctx, p0,
+                                          "Invalid surrogate pair");
+
+            /*
+             * Output "\\u", rewind, output the digits of `ctop'.
+             *
+             * When we get to what should have been the bottom half of the
+             * pair we'll necessarily fail to parse it as a normal escaped
+             * Unicode codepoint, and once again, rewind and output its digits.
+             */
+            if (p < pend)
+                *(p++) = '\\';
+            if (p < pend)
+                *(p++) = 'u';
+            ctx->p = p_save;
+            continue;
+        }
+
+        /* Finally decode the surrogate pair then encode as UTF-8 */
+        ctop -= 0xd800;
+        cbot -= 0xdc00;
+        if (!encode_utf8(ctx, &p, pend, 0x10000 + ((ctop << 10) | (cbot & 0x3ff))))
+            return parse_string_error(ctx, p0,
+                                      "Internal JSON string parse error");
     }
-    start = ++ctx->p;
 
-    while (ctx->p < ctx->pend) {
-       if (*ctx->p == '\n') {
-           ctx->lineno++;
-       } else if (*ctx->p == '\\') {
-           if (ctx->p + 1 == ctx->pend)
-               goto out;
-           ctx->p++;
-           quote = 1;
-       } else if (*ctx->p == '"') {
-           heim_object_t o;
-
-           if (quote) {
-               char *p0, *p;
-               p = p0 = malloc(ctx->p - start);
-               if (p == NULL)
-                   goto out;
-               while (start < ctx->p) {
-                   if (*start == '\\') {
-                       start++;
-                       /* XXX validate quoted char */
-                   }
-                   *p++ = *start++;
-               }
-               o = heim_string_create_with_bytes(p0, p - p0);
-               free(p0);
-           } else {
-               o = heim_string_create_with_bytes(start, ctx->p - start);
-               if (o == NULL) {
-                   ctx->error = heim_error_create_enomem();
-                   return NULL;
-               }
-
-               /* If we can decode as base64, then let's */
-               if (ctx->flags & HEIM_JSON_F_TRY_DECODE_DATA) {
-                   void *buf;
-                   size_t len;
-                   const char *s;
-
-                   s = heim_string_get_utf8(o);
-                   len = strlen(s);
-
-                   if (len >= 4 && strspn(s, base64_chars) >= len - 2) {
-                       buf = malloc(len);
-                       if (buf == NULL) {
-                           heim_release(o);
-                           ctx->error = heim_error_create_enomem();
-                           return NULL;
-                       }
-                       len = rk_base64_decode(s, buf);
-                       if (len == -1) {
-                           free(buf);
-                           return o;
-                       }
-                       heim_release(o);
-                       o = heim_data_ref_create(buf, len, free);
-                   }
-               }
-           }
-           ctx->p += 1;
+    if (p0 == NULL)
+        return heim_string_create("");
 
-           return o;
-       }
-       ctx->p += 1;
+    /* NUL-terminate for rk_base64_decode() and plain paranoia */
+    if (p0 != NULL && p == pend) {
+        /*
+        * Work out how far p is into p0 to re-esablish p after
+        * the realloc()
+        */
+        size_t p0_to_pend_len = (pend - p0);
+        char *tmp = realloc(p0, 1 + p0_to_pend_len);
+
+        if (tmp == NULL) {
+            ctx->error = heim_error_create_enomem();
+            free(p0);
+            return NULL;
+        }
+        /*
+         * We have three pointers, p, pend (which are the same)
+         * and p0, we want to keep them pointing into the same
+         * memory after the realloc()
+         */
+        p = tmp + p0_to_pend_len;
+
+        pend = p + 1;
+        p0 = tmp;
     }
-    out:
-    ctx->error = heim_error_create(EINVAL, "ran out of string");
-    return NULL;
+    *(p++) = '\0';
+
+    /* If there's embedded NULs, it's not a C string */
+    if (binary) {
+        o = heim_data_ref_create(p0, (p - 1) - p0, free);
+        return o;
+    }
+
+    /* Sadly this will copy `p0' */
+    o = heim_string_create_with_bytes(p0, p - p0);
+    free(p0);
+    return o;
 }
 
 static int
@@ -809,3 +1389,83 @@ heim_json_copy_serialize(heim_object_t obj, heim_json_flags_t flags, heim_error_
     }
     return str;
 }
+
+struct heim_eq_f_ctx {
+    heim_dict_t other;
+    int ret;
+};
+
+static void
+heim_eq_dict_iter_f(heim_object_t key, heim_object_t val, void *d)
+{
+    struct heim_eq_f_ctx *ctx = d;
+    heim_object_t other_val;
+
+    if (!ctx->ret)
+        return;
+
+    /*
+     * This doesn't work if the key is an array or a dict, which, anyways,
+     * isn't allowed in JSON, though we allow it.
+     */
+    other_val = heim_dict_get_value(ctx->other, key);
+    ctx->ret = heim_json_eq(val, other_val);
+}
+
+int
+heim_json_eq(heim_object_t a, heim_object_t b)
+{
+    heim_tid_t atid, btid;
+
+    if (a == b)
+        return 1;
+    if (a == NULL || b == NULL)
+        return 0;
+    atid = heim_get_tid(a);
+    btid = heim_get_tid(b);
+    if (atid != btid)
+        return 0;
+    switch (atid) {
+    case HEIM_TID_ARRAY: {
+        size_t len = heim_array_get_length(b);
+        size_t i;
+
+        if (heim_array_get_length(a) != len)
+            return 0;
+        for (i = 0; i < len; i++) {
+            if (!heim_json_eq(heim_array_get_value(a, i),
+                              heim_array_get_value(b, i)))
+                return 0;
+        }
+        return 1;
+    }
+    case HEIM_TID_DICT: {
+        struct heim_eq_f_ctx ctx;
+
+        ctx.other = b;
+        ctx.ret = 1;
+        heim_dict_iterate_f(a, &ctx, heim_eq_dict_iter_f);
+
+        if (ctx.ret) {
+            ctx.other = a;
+            heim_dict_iterate_f(b, &ctx, heim_eq_dict_iter_f);
+        }
+        return ctx.ret;
+    }
+    case HEIM_TID_STRING:
+        return strcmp(heim_string_get_utf8(a), heim_string_get_utf8(b)) == 0;
+    case HEIM_TID_DATA: {
+        return heim_data_get_length(a) == heim_data_get_length(b) &&
+               memcmp(heim_data_get_ptr(a), heim_data_get_ptr(b),
+                      heim_data_get_length(a)) == 0;
+    }
+    case HEIM_TID_NUMBER:
+        return heim_number_get_long(a) == heim_number_get_long(b);
+    case HEIM_TID_NULL:
+    case HEIM_TID_BOOL:
+        return heim_bool_val(a) == heim_bool_val(b);
+    default:
+        break;
+    }
+    return 0;
+}
index 24295b5adbcbb52c933360b52624628d02d98756..1d79c7e45b96fe0865f7d71fd556028a82f27837 100644 (file)
@@ -1051,7 +1051,8 @@ heim_audit_trail(heim_svc_req_desc r, heim_error_code ret, const char *retname)
        break;
     default:
         /* Wish we had a com_err number->symbolic name function */
-        (void) snprintf(retvalbuf, sizeof(retvalbuf), "UNKNOWN-%d", ret);
+        (void) snprintf(retvalbuf, sizeof(retvalbuf), "UNKNOWN-%d",
+                        ret ? ret : r->error_code);
        retval = retvalbuf;
        break;
     }
index f942447163db434f3e15dcefdbd266d12bf49272..5e79e00b18cb2397b3b42ecce7a73e0ad0473f2a 100644 (file)
@@ -153,7 +153,8 @@ heim_string_create_with_bytes(const void *data, size_t len)
 
     s = _heim_alloc_object(&_heim_string_object, len + 1);
     if (s) {
-       memcpy(s, data, len);
+        if (len)
+            memcpy(s, data, len);
        ((char *)s)[len] = '\0';
     }
     return s;
@@ -238,7 +239,7 @@ heim_string_t
 __heim_string_constant(const char *_str)
 {
     static HEIMDAL_MUTEX mutex = HEIMDAL_MUTEX_INITIALIZER;
-    static heim_base_once_t once;
+    static heim_base_once_t once = HEIM_BASE_ONCE_INIT;
     static heim_dict_t dict = NULL;
     heim_string_t s, s2;
 
index be6c860e26b9d201134dcda9b6176b8c5b889929..6d0668663af7036f104c9acc6eb55f1cf6b77a89 100644 (file)
@@ -53,6 +53,7 @@
 #include <sys/stat.h>
 #ifndef WIN32
 #include <sys/file.h>
+#include <locale.h>
 #endif
 #ifdef HAVE_IO_H
 #include <io.h>
@@ -244,26 +245,318 @@ test_json(void)
     };
     char *s;
     size_t i, k;
-    heim_object_t o, o2;
+    heim_object_t o, o2, o3;
     heim_string_t k1 = heim_string_create("k1");
 
     o = heim_json_create("\"string\"", 10, 0, NULL);
     heim_assert(o != NULL, "string");
     heim_assert(heim_get_tid(o) == heim_string_get_type_id(), "string-tid");
     heim_assert(strcmp("string", heim_string_get_utf8(o)) == 0, "wrong string");
+    o2 = heim_json_copy_serialize(o, 0, NULL);
+    o3 = heim_json_create(heim_string_get_utf8(o2), 10, 0, NULL);
+    heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip");
+    heim_release(o3);
+    heim_release(o2);
+    heim_release(o);
+
+    /*
+     * Test string escaping:
+     *
+     *  - C-like must-escapes
+     *  - ASCII control character must-escapes
+     *  - surrogate pairs
+     *
+     * We test round-tripping.  First we parse, then we serialize, then parse,
+     * then compare the second parse to the first for equality.
+     *
+     * We do compare serialized forms in spite of their not being canonical.
+     * That means that some changes to serialization can cause failures here.
+     */
+    o = heim_json_create("\""
+        "\\b\\f\\n\\r\\t"   /* ASCII C-like escapes */
+        "\x1e"              /* ASCII control character w/o C-like escape */
+        "\\u00e1"           /* &aacute; */
+        "\\u07ff"
+        "\\u0801"
+        "\\u8001"
+        "\\uD834\\udd1e"    /* U+1D11E, as shown in RFC 7159 */
+        "\"", 10, 0, NULL);
+    heim_assert(o != NULL, "string");
+    heim_assert(heim_get_tid(o) == heim_string_get_type_id(), "string-tid");
+    heim_assert(strcmp(
+        "\b\f\n\r\t"
+        "\x1e"
+        "\xc3\xa1"
+        "\xdf\xbf"
+        "\xe0\xA0\x81"
+        "\xe8\x80\x81"
+        "\xf0\x9d\x84\x9e", heim_string_get_utf8(o)) == 0, "wrong string");
+    o2 = heim_json_copy_serialize(o,
+                                  HEIM_JSON_F_STRICT |
+                                  HEIM_JSON_F_NO_ESCAPE_NON_ASCII, NULL);
+    heim_assert(strcmp("\"\\b\\f\\n\\r\\t\\u001Eá߿ࠁ老\\uD834\\uDD1E\"",
+                       heim_string_get_utf8(o2)) == 0,
+                "JSON encoding changed; please check that it is till valid");
+    o3 = heim_json_create(heim_string_get_utf8(o2), 10, HEIM_JSON_F_STRICT, NULL);
+    heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip");
+    heim_release(o3);
+    heim_release(o2);
+    heim_release(o);
+
+    o = heim_json_create("\""
+        "\\b\\f\\n\\r\\t"   /* ASCII C-like escapes */
+        "\x1e"              /* ASCII control character w/o C-like escape */
+        "\xc3\xa1"
+        "\xdf\xbf"
+        "\xe0\xa0\x81"
+        "\xE8\x80\x81"
+        "\\uD834\\udd1e"    /* U+1D11E, as shown in RFC 7159 */
+        "\"", 10, 0, NULL);
+    heim_assert(o != NULL, "string");
+    heim_assert(heim_get_tid(o) == heim_string_get_type_id(), "string-tid");
+    heim_assert(strcmp(
+        "\b\f\n\r\t"
+        "\x1e"
+        "\xc3\xa1"
+        "\xdf\xbf"
+        "\xe0\xA0\x81"
+        "\xe8\x80\x81"
+        "\xf0\x9d\x84\x9e", heim_string_get_utf8(o)) == 0, "wrong string");
+    o2 = heim_json_copy_serialize(o,
+                                  HEIM_JSON_F_STRICT |
+                                  HEIM_JSON_F_NO_ESCAPE_NON_ASCII, NULL);
+    heim_assert(strcmp("\"\\b\\f\\n\\r\\t\\u001Eá߿ࠁ老\\uD834\\uDD1E\"",
+                       heim_string_get_utf8(o2)) == 0,
+                "JSON encoding changed; please check that it is till valid");
+    o3 = heim_json_create(heim_string_get_utf8(o2), 10, HEIM_JSON_F_STRICT, NULL);
+    heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip");
+    heim_release(o3);
+    heim_release(o2);
+    heim_release(o);
+
+    /*
+     * Test HEIM_JSON_F_ESCAPE_NON_ASCII.
+     *
+     * Also test that we get escaped non-ASCII because we're in a not-UTF-8
+     * locale, since we setlocale(LC_ALL, "C"), so we should escape non-ASCII
+     * by default.
+     */
+    o = heim_json_create("\""
+        "\\b\\f\\n\\r\\t"   /* ASCII C-like escapes */
+        "\x1e"              /* ASCII control character w/o C-like escape */
+        "\xc3\xa1"
+        "\xdf\xbf"
+        "\xe0\xa0\x81"
+        "\xE8\x80\x81"
+        "\\uD834\\udd1e"    /* U+1D11E, as shown in RFC 7159 */
+        "\"", 10, 0, NULL);
+    heim_assert(o != NULL, "string");
+    heim_assert(heim_get_tid(o) == heim_string_get_type_id(), "string-tid");
+    heim_assert(strcmp(
+        "\b\f\n\r\t"
+        "\x1e"
+        "\xc3\xa1"
+        "\xdf\xbf"
+        "\xe0\xA0\x81"
+        "\xe8\x80\x81"
+        "\xf0\x9d\x84\x9e", heim_string_get_utf8(o)) == 0, "wrong string");
+    o2 = heim_json_copy_serialize(o,
+                                  HEIM_JSON_F_STRICT |
+                                  HEIM_JSON_F_ESCAPE_NON_ASCII, NULL);
+    heim_assert(strcmp("\"\\b\\f\\n\\r\\t\\u001E\\u00E1\\u07FF\\u0801\\u8001"
+                       "\\uD834\\uDD1E\"",
+                       heim_string_get_utf8(o2)) == 0,
+                "JSON encoding changed; please check that it is till valid");
+    heim_release(o2);
+    o2 = heim_json_copy_serialize(o, HEIM_JSON_F_STRICT, NULL);
+    heim_assert(strcmp("\"\\b\\f\\n\\r\\t\\u001E\\u00E1\\u07FF\\u0801\\u8001"
+                       "\\uD834\\uDD1E\"",
+                       heim_string_get_utf8(o2)) == 0,
+                "JSON encoding changed; please check that it is till valid");
+    o3 = heim_json_create(heim_string_get_utf8(o2), 10, HEIM_JSON_F_STRICT, NULL);
+    heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip");
+    heim_release(o3);
+    heim_release(o2);
+    heim_release(o);
+
+    /* Test rejection of unescaped ASCII control characters */
+    o = heim_json_create("\"\b\\f\"", 10, HEIM_JSON_F_STRICT, NULL);
+    heim_assert(o == NULL, "strict parse accepted bad input");
+    o = heim_json_create("\"\b\x1e\"", 10, HEIM_JSON_F_STRICT, NULL);
+    heim_assert(o == NULL, "strict parse accepted bad input");
+
+    o = heim_json_create("\"\b\\f\"", 10, 0, NULL);
+    heim_assert(o != NULL, "string");
+    heim_assert(heim_get_tid(o) == heim_string_get_type_id(), "string-tid");
+    heim_assert(strcmp("\b\f", heim_string_get_utf8(o)) == 0, "wrong string");
+    o2 = heim_json_copy_serialize(o,
+                                  HEIM_JSON_F_STRICT |
+                                  HEIM_JSON_F_NO_ESCAPE_NON_ASCII, NULL);
+    heim_assert(strcmp("\"\\b\\f\"", heim_string_get_utf8(o2)) == 0,
+                "JSON encoding changed; please check that it is till valid");
+    o3 = heim_json_create(heim_string_get_utf8(o2), 10, HEIM_JSON_F_STRICT, NULL);
+    heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip");
+    heim_release(o3);
+    heim_release(o2);
+    heim_release(o);
+
+    /* Test bogus backslash escape */
+    o = heim_json_create("\""
+        "\\ "
+        "\"", 10, HEIM_JSON_F_STRICT, NULL);
+    heim_assert(o == NULL, "malformed string accepted");
+    o = heim_json_create("\""
+        "\\ "
+        "\"", 10, 0, NULL);
+    heim_assert(o != NULL, "malformed string rejected (not strict)");
+    heim_assert(heim_get_tid(o) == heim_string_get_type_id(), "string-tid");
+    heim_assert(strcmp(" ", heim_string_get_utf8(o)) == 0, "wrong string");
+    o2 = heim_json_copy_serialize(o,
+                                  HEIM_JSON_F_STRICT |
+                                  HEIM_JSON_F_NO_ESCAPE_NON_ASCII, NULL);
+    heim_assert(strcmp("\" \"", heim_string_get_utf8(o2)) == 0,
+                "JSON encoding changed; please check that it is till valid");
+    o3 = heim_json_create(heim_string_get_utf8(o2), 10, HEIM_JSON_F_STRICT, NULL);
+    heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip");
+    heim_release(o3);
+    heim_release(o2);
+    heim_release(o);
+
+    /* Test truncated surrogate encoding (bottom code unit) */
+    o = heim_json_create("\""
+        "\xE8\x80\x81"
+        "\\uD834\\udd"
+        "\"", 10, HEIM_JSON_F_STRICT, NULL);
+    heim_assert(o == NULL, "malformed string accepted");
+    o = heim_json_create("\""
+        "\xE8\x80\x81"
+        "\\uD834\\udd"
+        "\"", 10, 0, NULL);
+    heim_assert(o != NULL, "malformed string rejected (not strict)");
+    heim_assert(heim_get_tid(o) == heim_string_get_type_id(), "string-tid");
+    heim_assert(strcmp(
+        "\xe8\x80\x81"
+        "\\uD834\\udd", heim_string_get_utf8(o)) == 0, "wrong string");
+    o2 = heim_json_copy_serialize(o,
+                                  HEIM_JSON_F_STRICT |
+                                  HEIM_JSON_F_NO_ESCAPE_NON_ASCII, NULL);
+    heim_assert(strcmp("\"老\\\\uD834\\\\udd\"",
+                       heim_string_get_utf8(o2)) == 0,
+                "JSON encoding changed; please check that it is till valid");
+    o3 = heim_json_create(heim_string_get_utf8(o2), 10, HEIM_JSON_F_STRICT, NULL);
+    heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip");
+    heim_release(o3);
+    heim_release(o2);
+    heim_release(o);
+
+    /* Test truncated surrogate encodings (top code unit) */
+    o = heim_json_create("\""
+        "\xE8\x80\x81"
+        "\\uD83"
+        "\"", 10, HEIM_JSON_F_STRICT, NULL);
+    heim_assert(o == NULL, "malformed string accepted");
+    o = heim_json_create("\""
+        "\xE8\x80\x81"
+        "\\uD83"
+        "\"", 10, 0, NULL);
+    heim_assert(o != NULL, "malformed string rejected (not strict)");
+    heim_assert(heim_get_tid(o) == heim_string_get_type_id(), "string-tid");
+    heim_assert(strcmp(
+        "\xe8\x80\x81"
+        "\\uD83", heim_string_get_utf8(o)) == 0, "wrong string");
+    o2 = heim_json_copy_serialize(o,
+                                  HEIM_JSON_F_STRICT |
+                                  HEIM_JSON_F_NO_ESCAPE_NON_ASCII, NULL);
+    heim_assert(strcmp("\"老\\\\uD83\"",
+                       heim_string_get_utf8(o2)) == 0,
+                "JSON encoding changed; please check that it is till valid");
+    o3 = heim_json_create(heim_string_get_utf8(o2), 10, HEIM_JSON_F_STRICT, NULL);
+    heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip");
+    heim_release(o3);
+    heim_release(o2);
     heim_release(o);
 
+    /*
+     * Test handling of truncated UTF-8 multi-byte sequences.
+     */
+    o = heim_json_create("\""
+        "\xE8\x80"
+        "\"", 10, 0, NULL);
+    heim_assert(o != NULL, "malformed string rejected (not strict)");
+    heim_assert(heim_get_tid(o) == heim_string_get_type_id(), "string-tid");
+    heim_assert(strcmp("\xe8\x80",
+                       heim_string_get_utf8(o)) == 0, "wrong string");
+    o2 = heim_json_copy_serialize(o,
+                                  HEIM_JSON_F_STRICT |
+                                  HEIM_JSON_F_NO_ESCAPE_NON_ASCII, NULL);
+    heim_assert(o2 == NULL, "malformed string serialized");
+    o2 = heim_json_copy_serialize(o, HEIM_JSON_F_NO_ESCAPE_NON_ASCII, NULL);
+    o3 = heim_json_create(heim_string_get_utf8(o2), 10, HEIM_JSON_F_STRICT, NULL);
+    heim_assert(o3 == NULL, "malformed string accepted (not strict)");
+    o3 = heim_json_create(heim_string_get_utf8(o2), 10, 0, NULL);
+    heim_assert(strcmp("\xe8\x80",
+                       heim_string_get_utf8(o3)) == 0, "wrong string");
+    heim_release(o3);
+    heim_release(o2);
+    heim_release(o);
+
+    /* Test handling of unescaped / embedded newline */
+    o = heim_json_create("\"\n\"", 10, HEIM_JSON_F_STRICT, NULL);
+    heim_assert(o == NULL, "malformed string accepted (strict)");
+    o = heim_json_create("\"\n\"", 10, 0, NULL);
+    heim_assert(o != NULL, "malformed string rejected (not strict)");
+    heim_assert(heim_get_tid(o) == heim_string_get_type_id(), "string-tid");
+    heim_assert(strcmp("\n", heim_string_get_utf8(o)) == 0, "wrong string");
+    o2 = heim_json_copy_serialize(o, HEIM_JSON_F_STRICT, NULL);
+    heim_assert(o2 != NULL, "string not serialized");
+    o3 = heim_json_create(heim_string_get_utf8(o2), 10, HEIM_JSON_F_STRICT, NULL);
+    heim_assert(o3 != NULL, "string not accepted");
+    heim_assert(strcmp("\n", heim_string_get_utf8(o3)) == 0, "wrong string");
+    heim_release(o3);
+    heim_release(o2);
+    heim_release(o);
+
+    /* Test handling of embedded NULs (must decode as data, not string) */
+    o = heim_json_create("\"\\u0000\"", 10, HEIM_JSON_F_STRICT, NULL);
+    heim_assert(o != NULL, "string with NULs rejected");
+    heim_assert(heim_get_tid(o) == heim_data_get_type_id(), "data-tid");
+    heim_assert(heim_data_get_length(o) == 1, "wrong data length");
+    heim_assert(((const char *)heim_data_get_ptr(o))[0] == '\0',
+                "wrong data NUL");
+    o2 = heim_json_copy_serialize(o, 0, NULL);
+    heim_assert(o2 != NULL, "data not serialized");
+    heim_release(o2);
+    heim_release(o);
+
+    /*
+     * Note that the trailing ']' is not part of the JSON text (which is just a
+     * string).
+     */
     o = heim_json_create(" \"foo\\\"bar\" ]", 10, 0, NULL);
     heim_assert(o != NULL, "string");
     heim_assert(heim_get_tid(o) == heim_string_get_type_id(), "string-tid");
     heim_assert(strcmp("foo\"bar", heim_string_get_utf8(o)) == 0, "wrong string");
+    o2 = heim_json_copy_serialize(o, 0, NULL);
+    o3 = heim_json_create(heim_string_get_utf8(o2), 10, 0, NULL);
+    heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip");
+    heim_release(o3);
+    heim_release(o2);
     heim_release(o);
 
     o = heim_json_create(" { \"key\" : \"value\" }", 10, 0, NULL);
     heim_assert(o != NULL, "dict");
     heim_assert(heim_get_tid(o) == heim_dict_get_type_id(), "dict-tid");
+    o2 = heim_json_copy_serialize(o, 0, NULL);
+    o3 = heim_json_create(heim_string_get_utf8(o2), 10, 0, NULL);
+    heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip");
+    heim_release(o3);
+    heim_release(o2);
     heim_release(o);
 
+    /*
+     * heim_json_eq() can't handle dicts with dicts as keys, so we don't check
+     * for round-tripping here
+     */
     o = heim_json_create("{ { \"k1\" : \"s1\", \"k2\" : \"s2\" } : \"s3\", "
                         "{ \"k3\" : \"s4\" } : -1 }", 10, 0, NULL);
     heim_assert(o != NULL, "dict");
@@ -281,6 +574,11 @@ test_json(void)
     o2 = heim_dict_copy_value(o, k1);
     heim_assert(heim_get_tid(o2) == heim_string_get_type_id(), "string-tid");
     heim_release(o2);
+    o2 = heim_json_copy_serialize(o, 0, NULL);
+    o3 = heim_json_create(heim_string_get_utf8(o2), 10, 0, NULL);
+    heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip");
+    heim_release(o3);
+    heim_release(o2);
     heim_release(o);
 
     o = heim_json_create(" { \"k1\" : { \"k2\" : \"s2\" } }", 10, 0, NULL);
@@ -289,6 +587,11 @@ test_json(void)
     o2 = heim_dict_copy_value(o, k1);
     heim_assert(heim_get_tid(o2) == heim_dict_get_type_id(), "dict-tid");
     heim_release(o2);
+    o2 = heim_json_copy_serialize(o, 0, NULL);
+    o3 = heim_json_create(heim_string_get_utf8(o2), 10, 0, NULL);
+    heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip");
+    heim_release(o3);
+    heim_release(o2);
     heim_release(o);
 
     o = heim_json_create("{ \"k1\" : 1 }", 10, 0, NULL);
@@ -297,26 +600,51 @@ test_json(void)
     o2 = heim_dict_copy_value(o, k1);
     heim_assert(heim_get_tid(o2) == heim_number_get_type_id(), "number-tid");
     heim_release(o2);
+    o2 = heim_json_copy_serialize(o, 0, NULL);
+    o3 = heim_json_create(heim_string_get_utf8(o2), 10, 0, NULL);
+    heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip");
+    heim_release(o3);
+    heim_release(o2);
     heim_release(o);
 
     o = heim_json_create("-10", 10, 0, NULL);
     heim_assert(o != NULL, "number");
     heim_assert(heim_get_tid(o) == heim_number_get_type_id(), "number-tid");
+    o2 = heim_json_copy_serialize(o, 0, NULL);
+    o3 = heim_json_create(heim_string_get_utf8(o2), 10, 0, NULL);
+    heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip");
+    heim_release(o3);
+    heim_release(o2);
     heim_release(o);
 
     o = heim_json_create("99", 10, 0, NULL);
     heim_assert(o != NULL, "number");
     heim_assert(heim_get_tid(o) == heim_number_get_type_id(), "number-tid");
+    o2 = heim_json_copy_serialize(o, 0, NULL);
+    o3 = heim_json_create(heim_string_get_utf8(o2), 10, 0, NULL);
+    heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip");
+    heim_release(o3);
+    heim_release(o2);
     heim_release(o);
 
     o = heim_json_create(" [ 1 ]", 10, 0, NULL);
     heim_assert(o != NULL, "array");
     heim_assert(heim_get_tid(o) == heim_array_get_type_id(), "array-tid");
+    o2 = heim_json_copy_serialize(o, 0, NULL);
+    o3 = heim_json_create(heim_string_get_utf8(o2), 10, 0, NULL);
+    heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip");
+    heim_release(o3);
+    heim_release(o2);
     heim_release(o);
 
     o = heim_json_create(" [ -1 ]", 10, 0, NULL);
     heim_assert(o != NULL, "array");
     heim_assert(heim_get_tid(o) == heim_array_get_type_id(), "array-tid");
+    o2 = heim_json_copy_serialize(o, 0, NULL);
+    o3 = heim_json_create(heim_string_get_utf8(o2), 10, 0, NULL);
+    heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip");
+    heim_release(o3);
+    heim_release(o2);
     heim_release(o);
 
     for (i = 0; i < (sizeof (j) / sizeof (j[0])); i++) {
@@ -325,6 +653,11 @@ test_json(void)
            fprintf(stderr, "Failed to parse this JSON: %s\n", j[i]);
            return 1;
        }
+        o2 = heim_json_copy_serialize(o, 0, NULL);
+        o3 = heim_json_create(heim_string_get_utf8(o2), 10, 0, NULL);
+        heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip");
+        heim_release(o3);
+        heim_release(o2);
        heim_release(o);
        /* Simple fuzz test */
        for (k = strlen(j[i]) - 1; k > 0; k--) {
@@ -945,6 +1278,11 @@ main(int argc, char **argv)
 {
     int res = 0;
 
+#ifndef WIN32
+    setlocale(LC_ALL, "C");
+    heim_assert(!heim_locale_is_utf8(), "setlocale(LC_ALL, \"C\") failed?");
+#endif
+
     res |= test_memory();
     res |= test_mutex();
     res |= test_rwlock();
index 928e86199955e899fb761c585a39310dad9c81c2..9493ee692363ae19c9bcb1f3f6619aa1be4f0a4d 100644 (file)
@@ -146,7 +146,9 @@ HEIMDAL_BASE_1.0 {
                heim_json_copy_serialize;
                heim_json_create;
                heim_json_create_with_bytes;
+               heim_json_eq;
                heim_load_plugins;
+               heim_locale_is_utf8;
                heim_log;
                heim_log_msg;
                _heim_make_permanent;
index a69ebffb04ecb246de7c0923287510619321d64e..3cb8437db28572fd721767a4b243d0ae7eb9f9b3 100644 (file)
@@ -264,10 +264,6 @@ dist_libgssapi_la_SOURCES  = \
        $(sanonsrc)
 
 nodist_libgssapi_la_SOURCES  = \
-       gkrb5_err.c \
-       gkrb5_err.h \
-       negoex_err.c \
-       negoex_err.h \
        $(BUILT_SOURCES)
 
 libgssapi_la_DEPENDENCIES = version-script.map
@@ -333,7 +329,13 @@ $(test_context_OBJECTS): $(BUILTHEADERS)
 
 $(libgssapi_la_OBJECTS): $(srcdir)/version-script.map
 
-BUILT_SOURCES = $(spnego_files) $(gssapi_files)
+BUILT_SOURCES = \
+       $(spnego_files) \
+       $(gssapi_files) \
+       gkrb5_err.c \
+       gkrb5_err.h \
+       negoex_err.c \
+       negoex_err.h
 
 $(libgssapi_la_OBJECTS): gkrb5_err.h negoex_err.h
 gkrb5_err.h: $(srcdir)/krb5/gkrb5_err.et
index 844fa4d3820630319de822f5a2f4eade8d33535f..f3f90521c3bd6ace7d4b49a85af0e84125d77549 100644 (file)
@@ -235,10 +235,9 @@ write_and_free_token(gss_buffer_t out, int negotiate)
                        printf("\n");
                if (len < inc)
                        inc = len;
-               ret = rk_base64_encode(p, inc, &outstr);
-               if (ret < 0) {
+               if (rk_base64_encode(p, inc, &outstr) < 0) {
                        fprintf(stderr, "Out of memory.\n");
-                       ret = 1;
+                       ret = errno;
                        goto bail;
                }
                 ret = 0;
@@ -247,6 +246,7 @@ write_and_free_token(gss_buffer_t out, int negotiate)
                p   += inc;
                len -= inc;
        } while (len > 0);
+        ret = 0;
 
 bail:
        gss_release_buffer(&min, out);
index 3e21336353458dba6a381813beea9e28d5981eca..bf7da11c754eba79942bc7f3760bf6c9d13a1214 100644 (file)
@@ -259,7 +259,7 @@ _gsskrb5_verify_8003_checksum(
     }
 
     if (input_chan_bindings != GSS_C_NO_CHANNEL_BINDINGS
-       && (memcmp(p, zeros, sizeof(zeros)) != 0 || client_asserted_cb)) {
+       && (ct_memcmp(p, zeros, sizeof(zeros)) != 0 || client_asserted_cb)) {
        if(hash_input_chan_bindings(input_chan_bindings, hash) != 0) {
            *minor_status = 0;
            return GSS_S_BAD_BINDINGS;
index 62b26ed7eb91a0d4602d9d8e213f4dc17bb4af27..a705d03a8756940155a82903a9327b0724031497 100644 (file)
@@ -932,7 +932,7 @@ OM_uint32 GSSAPI_CALLCONV _gsskrb5_init_sec_context
                        time_rec);
        if (ret != GSS_S_COMPLETE)
            break;
-        fallthrough;
+        HEIM_FALLTHROUGH;
     case INITIATOR_RESTART:
        ret = init_auth_restart(minor_status,
                                cred,
index 0fc8f019d645d36fec5ce84629d2a30b5bd1fd51..6147eec55e09bba8a6c517b347eb61c85b71fc1a 100644 (file)
@@ -588,7 +588,7 @@ _netlogon_unwrap_iov(OM_uint32 *minor_status,
 
     /* [MS-NRPC] 3.3.4.2.2.10: verify signature */
     _netlogon_digest(ctx, sig, iov, iov_count, checksum);
-    if (memcmp(sig->Checksum, checksum, _netlogon_checksum_length(sig)) != 0)
+    if (ct_memcmp(sig->Checksum, checksum, _netlogon_checksum_length(sig)) != 0)
         return GSS_S_BAD_SIG;
 
     HEIMDAL_MUTEX_lock(&ctx->Mutex);
index efa71d911dcbd08aa25b206126aa950d755c36e0..d1a115ff8e34bcab6cfef7435a2b2954c9a8cb96 100644 (file)
@@ -230,7 +230,7 @@ v2_verify_message(gss_buffer_t in,
     if (ret)
        return ret;
 
-    if (memcmp(checksum, out, 16) != 0)
+    if (ct_memcmp(checksum, out, 16) != 0)
        return GSS_S_BAD_MIC;
 
     return GSS_S_COMPLETE;
index ac174180fb9262df86499fc36cd7d2e7cad97ab4..32f87b47137e12ce6b3349d44f475b653dbcee4f 100644 (file)
@@ -851,7 +851,7 @@ DES_string_to_key(const char *str, DES_cblock *key)
        k[7] ^= 0xF0;
     DES_set_key(key, &ks);
     DES_cbc_cksum(s, key, len, &ks, key);
-    memset(&ks, 0, sizeof(ks));
+    memset_s(&ks, sizeof(ks), 0, sizeof(ks));
     DES_set_odd_parity(key);
     if (DES_is_weak_key(key))
        k[7] ^= 0xF0;
index 5d2d214f7526a217404a98714aba7a793f6de576..b3b25e5186d1fda1ab5e45b704a19c49e3058269 100644 (file)
@@ -145,7 +145,7 @@ DH_free(DH *dh)
     free_if(dh->counter);
 #undef free_if
 
-    memset(dh, 0, sizeof(*dh));
+    memset_s(dh, sizeof(*dh), 0, sizeof(*dh));
     free(dh);
 }
 
index bbb34601c31fca8b988df5b0cf97864e5c4ba95a..eac0ae6498ec2cd2a14adcb91774d29b003dc908 100644 (file)
@@ -70,7 +70,7 @@ DSA_free(DSA *dsa)
     free_if(dsa->r);
 #undef free_if
 
-    memset(dsa, 0, sizeof(*dsa));
+    memset_s(dsa, sizeof(*dsa), 0, sizeof(*dsa));
     free(dsa);
 
 }
index 3dae960fd0c8d64c521e1abd00427b2d14c33c4f..6a79b7c9907928964ff6a86ba50d9ab366f4019e 100644 (file)
@@ -87,7 +87,7 @@ ENGINE_finish(ENGINE *engine)
     if (engine->dso_handle)
        dlclose(engine->dso_handle);
 
-    memset(engine, 0, sizeof(*engine));
+    memset_s(engine, sizeof(*engine), 0, sizeof(*engine));
     engine->references = -1;
 
 
index a651184c6ea003261755f9022f91a9674602ae5a..ca02862bf683b72d50d2a97936661f56c21c8b6b 100644 (file)
@@ -204,7 +204,7 @@ get_EVP_CIPHER_once_cb(void *d)
      */
     ossl_evp = EVP_get_cipherbynid(arg->nid);
     if (ossl_evp == NULL) {
-        (void) memset(hc_evp, 0, sizeof(*hc_evp));
+        (void) memset_s(hc_evp, sizeof(*hc_evp), 0, sizeof(*hc_evp));
 #if HCRYPTO_FALLBACK
         *arg->hc_memoizep = arg->fallback;
 #endif
@@ -348,7 +348,7 @@ get_EVP_MD_once_cb(void *d)
     *arg->ossl_memoizep = ossl_evp = EVP_get_digestbynid(arg->nid);
 
     if (ossl_evp == NULL) {
-        (void) memset(hc_evp, 0, sizeof(*hc_evp));
+        (void) memset_s(hc_evp, sizeof(*hc_evp), 0, sizeof(*hc_evp));
 #if HCRYPTO_FALLBACK
         *arg->hc_memoizep = arg->fallback;
 #endif
index 9cced4c536ce6c5b609cd7420f7940eeeaca3e59..320e85283f7ad6ca1b4da7cf741011321fe6cf6c 100644 (file)
@@ -189,12 +189,12 @@ EVP_MD_CTX_cleanup(EVP_MD_CTX *ctx) HC_DEPRECATED
        if (!ret)
            return ret;
     } else if (ctx->md) {
-       memset(ctx->ptr, 0, ctx->md->ctx_size);
+       memset_s(ctx->ptr, ctx->md->ctx_size, 0, ctx->md->ctx_size);
     }
     ctx->md = NULL;
     ctx->engine = NULL;
     free(ctx->ptr);
-    memset(ctx, 0, sizeof(*ctx));
+    memset_s(ctx, sizeof(*ctx), 0, sizeof(*ctx));
     return 1;
 }
 
@@ -607,7 +607,7 @@ EVP_CIPHER_CTX_cleanup(EVP_CIPHER_CTX *c)
     }
     if (c->cipher_data) {
         if (c->cipher)
-            memset(c->cipher_data, 0, c->cipher->ctx_size);
+            memset_s(c->cipher_data, c->cipher->ctx_size, 0, c->cipher->ctx_size);
        free(c->cipher_data);
        c->cipher_data = NULL;
     }
@@ -905,7 +905,7 @@ EVP_CipherUpdate(EVP_CIPHER_CTX *ctx, void *out, int *outlen,
        /* fill in local buffer and encrypt */
        memcpy(ctx->buf + ctx->buf_len, in, left);
        ret = (*ctx->cipher->do_cipher)(ctx, out, ctx->buf, blocksize);
-       memset(ctx->buf, 0, blocksize);
+       memset_s(ctx->buf, blocksize, 0, blocksize);
        if (ret != 1)
            return ret;
 
@@ -966,7 +966,7 @@ EVP_CipherFinal_ex(EVP_CIPHER_CTX *ctx, void *out, int *outlen)
        /* zero fill local buffer */
        memset(ctx->buf + ctx->buf_len, 0, left);
        ret = (*ctx->cipher->do_cipher)(ctx, out, ctx->buf, blocksize);
-       memset(ctx->buf, 0, blocksize);
+       memset_s(ctx->buf, blocksize, 0, blocksize);
        if (ret != 1)
            return ret;
 
index 6b387ae90dc71e23db38756364b0a9b19ec86008..adccee76b2673b0edbe98551fa74414ec047598e 100644 (file)
@@ -46,17 +46,17 @@ void
 HMAC_CTX_cleanup(HMAC_CTX *ctx)
 {
     if (ctx->buf) {
-       memset(ctx->buf, 0, ctx->key_length);
+       memset_s(ctx->buf, ctx->key_length, 0, ctx->key_length);
        free(ctx->buf);
        ctx->buf = NULL;
     }
     if (ctx->opad) {
-       memset(ctx->opad, 0, EVP_MD_block_size(ctx->md));
+       memset_s(ctx->opad, EVP_MD_block_size(ctx->md), 0, EVP_MD_block_size(ctx->md));
        free(ctx->opad);
        ctx->opad = NULL;
     }
     if (ctx->ipad) {
-       memset(ctx->ipad, 0, EVP_MD_block_size(ctx->md));
+       memset_s(ctx->ipad, EVP_MD_block_size(ctx->md), 0, EVP_MD_block_size(ctx->md));
        free(ctx->ipad);
        ctx->ipad = NULL;
     }
index da41e6d83ee02ffde93a0046b635ad9e21907f30..0170d416a1c1dc58f68260d482e8677d6a1c9e12 100644 (file)
@@ -133,6 +133,6 @@ MD2_Final (void *res, struct md2 *m)
     MD2_Update(m, pad, 16);
 
     memcpy(res, m->state, MD2_DIGEST_LENGTH);
-    memset(m, 0, sizeof(*m));
+    memset_s(m, sizeof(*m), 0, sizeof(*m));
     return 1;
 }
index 037fa7436a4316613791d6d3e9b5547d5f48f912..30721160a2ae59fc1c3e069e956959e4407cf566 100644 (file)
@@ -77,11 +77,11 @@ pwd_dialog(char *buf, int size)
     {
     case IDOK:
        strlcpy(buf, passwd, size);
-       memset (passwd, 0, sizeof(passwd));
+       memset_s (passwd, sizeof(passwd), 0, sizeof(passwd));
        return 0;
     case IDCANCEL:
     default:
-       memset (passwd, 0, sizeof(passwd));
+       memset_s (passwd, sizeof(passwd), 0, sizeof(passwd));
        return 1;
     }
 }
index 313b86f83cf2a0491860fd65ba691d1332b4bc2c..74ba12396fd8be0f7df434e8197b01c251b25581 100644 (file)
@@ -336,7 +336,7 @@ add_entropy(FState * st, const unsigned char *data, unsigned len)
        st->pool0_bytes += len;
 
     memset_s(hash, sizeof(hash), 0, sizeof(hash));
-    memset_s(&md, sizeof(hash), 0, sizeof(md));
+    memset_s(&md, sizeof(md), 0, sizeof(md));
 }
 
 /*
index 02b684cbd4b0a21ea31c42b5818de70d7d92e1f6..53d32cf0417ea74c6e7c82caa9c8892fe4d25b17 100644 (file)
@@ -105,7 +105,7 @@ RC2_set_key(RC2_KEY *key, int len, const unsigned char *data, int bits)
 
     for (j = 0; j < 64; j++)
        key->data[j] = k[(j * 2) + 0] | (k[(j * 2) + 1] << 8);
-    memset(k, 0, sizeof(k));
+    memset_s(k, sizeof(k), 0, sizeof(k));
 }
 
 #define ROT16L(w,n)  ((w<<n)|(w>>(16-n)))
index 6172b25413f1fe8deee264daf58e394416ba8e89..31470d0069a33808d797f909f9928ce031af670a 100644 (file)
@@ -160,7 +160,7 @@ RSA_free(RSA *rsa)
     free_if(rsa->iqmp);
 #undef free_if
 
-    memset(rsa, 0, sizeof(*rsa));
+    memset_s(rsa, sizeof(*rsa), 0, sizeof(*rsa));
     free(rsa);
 }
 
@@ -426,7 +426,7 @@ RSA_verify(int type, const unsigned char *from, unsigned int flen,
            return -4;
        }
 
-       if (flen != di.digest.length || memcmp(di.digest.data, from, flen) != 0) {
+       if (flen != di.digest.length || ct_memcmp(di.digest.data, from, flen) != 0) {
            free_DigestInfo(&di);
            return -5;
        }
index 89ab15d9d3e50bc65778b7a09a30d2256c0ddfd2..4a55995324364c493e23e64f16855c0571427319 100644 (file)
@@ -17,7 +17,9 @@ endif
 BUILT_SOURCES = \
        $(gen_files_hdb)        \
        hdb_err.c \
-       hdb_err.h
+       hdb_err.h \
+       $(srcdir)/hdb-protos.h \
+       $(srcdir)/hdb-private.h
 
 gen_files_hdb = \
        asn1_Event.c \
index a92cc1372db5ca481eed5671590875eb52b7514d..56e582abaa85b76ac1debb471c21ba0f39d03729 100644 (file)
@@ -181,6 +181,7 @@ fetch_entry_or_alias(krb5_context context,
         ret = decode_HDB_EntryOrAlias(value.data, value.length, &eoa, NULL);
     if (ret == 0 && eoa.element == choice_HDB_EntryOrAlias_entry) {
         *entry = eoa.u.entry;
+        entry->aliased = 0;
     } else if (ret == 0 && eoa.element == choice_HDB_EntryOrAlias_alias) {
         krb5_data_free(&key);
        ret = hdb_principal2key(context, eoa.u.alias.principal, &key);
@@ -192,6 +193,7 @@ fetch_entry_or_alias(krb5_context context,
             /* No alias chaining */
             ret = hdb_value2entry(context, &value, entry);
        krb5_free_principal(context, eoa.u.alias.principal);
+        entry->aliased = 1;
     } else if (ret == 0)
         ret = ENOTSUP;
     if (ret == 0 && enterprise_principal) {
@@ -203,6 +205,7 @@ fetch_entry_or_alias(krb5_context context,
        entry->flags.force_canonicalize = 1;
     }
 
+#if 0
     /* HDB_F_GET_ANY indicates request originated from KDC (not kadmin) */
     if (ret == 0 && eoa.element == choice_HDB_EntryOrAlias_alias &&
         (flags & (HDB_F_CANON|HDB_F_GET_ANY)) == 0) {
@@ -211,6 +214,7 @@ fetch_entry_or_alias(krb5_context context,
         free_HDB_entry(entry);
         ret = HDB_ERR_NOENTRY;
     }
+#endif
 
     krb5_free_principal(context, enterprise_principal);
     krb5_data_free(&value);
@@ -219,11 +223,58 @@ fetch_entry_or_alias(krb5_context context,
     return ret;
 }
 
+/*
+ * We have only one type of aliases in our HDB entries, but we really need two:
+ * hard and soft.
+ *
+ * Hard aliases should be treated as if they were distinct principals with the
+ * same keys.
+ *
+ * Soft aliases should be treated as configuration to issue referrals, and they
+ * can only result in referrals to other realms.
+ *
+ * Rather than add a type of aliases, we'll use a convention where the form of
+ * the target of the alias indicates whether the alias is hard or soft.
+ *
+ * TODO We could also use an attribute of the aliased entry.
+ */
+static int
+is_soft_alias_p(krb5_context context,
+                krb5_const_principal principal,
+                unsigned int flags,
+                hdb_entry *h)
+{
+    /* Target is a WELLKNOWN/REFERRALS/TARGET/... -> soft alias */
+    if (krb5_principal_get_num_comp(context, h->principal) >= 3 &&
+        strcmp(krb5_principal_get_comp_string(context, h->principal, 0),
+               KRB5_WELLKNOWN_NAME) == 0 &&
+        strcmp(krb5_principal_get_comp_string(context, h->principal, 1),
+               "REFERRALS") == 0 &&
+        strcmp(krb5_principal_get_comp_string(context, h->principal, 2),
+               "TARGET") == 0)
+        return 1;
+
+    /*
+     * Pre-8.0 we had only soft aliases for a while, and one site used aliases
+     * of referrals-targetNN@TARGET-REALM.
+     */
+    if (krb5_principal_get_num_comp(context, h->principal) == 1 &&
+        strncmp("referrals-target",
+                krb5_principal_get_comp_string(context, h->principal, 0),
+                sizeof("referrals-target") - 1) == 0)
+        return 1;
+
+    /* All other cases are hard aliases */
+    return 0;
+}
+
 krb5_error_code
 _hdb_fetch_kvno(krb5_context context, HDB *db, krb5_const_principal principal,
                unsigned flags, krb5_kvno kvno, hdb_entry *entry)
 {
     krb5_error_code ret;
+    int soft_aliased = 0;
+    int same_realm;
 
     ret = fetch_entry_or_alias(context, db, principal, flags, entry);
     if (ret)
@@ -278,7 +329,54 @@ _hdb_fetch_kvno(krb5_context context, HDB *db, krb5_const_principal principal,
        }
     }
 
-    return 0;
+    if (!entry->aliased)
+        return 0;
+
+    soft_aliased = is_soft_alias_p(context, principal, flags, entry);
+
+    /* Never return HDB_ERR_WRONG_REALM to kadm5 or other non-KDC callers */
+    if ((flags & HDB_F_ADMIN_DATA))
+        return 0;
+
+    same_realm = krb5_realm_compare(context, principal, entry->principal);
+
+    if (entry->aliased && !soft_aliased) {
+        /*
+         * This is a hard alias.  We'll make the entry's name be the same as
+         * the alias.
+         *
+         * Except, we allow for disabling this for same-realm aliases, mainly
+         * for our tests.
+         */
+        if (same_realm &&
+            krb5_config_get_bool_default(context, NULL, FALSE, "hdb",
+                                         "same_realm_aliases_are_soft", NULL))
+            return 0;
+
+        /* EPNs are always soft */
+        if (principal->name.name_type != KRB5_NT_ENTERPRISE_PRINCIPAL) {
+            krb5_free_principal(context, entry->principal);
+            ret = krb5_copy_principal(context, principal, &entry->principal);
+            if (ret) {
+                hdb_free_entry(context, db, entry);
+                return ret;
+            }
+        }
+        return 0;
+    }
+
+    /* Same realm -> not a referral, therefore this is a hard alias */
+    if (same_realm) {
+        if (soft_aliased) {
+            /* Soft alias to the same realm?!  No. */
+            hdb_free_entry(context, db, entry);
+            return HDB_ERR_NOENTRY;
+        }
+        return 0;
+    }
+
+    /* Not same realm && not hard alias */
+    return HDB_ERR_WRONG_REALM;
 }
 
 static krb5_error_code
@@ -313,6 +411,8 @@ hdb_remove_aliases(krb5_context context, HDB *db, krb5_data *key)
         if (code == 0) {
             code = db->hdb__del(context, db, akey);
             krb5_data_free(&akey);
+            if (code == HDB_ERR_NOENTRY)
+                code = 0;
         }
        if (code) {
            free_HDB_entry(&oldentry);
@@ -348,6 +448,12 @@ hdb_add_aliases(krb5_context context, HDB *db,
         if (code == 0) {
             code = db->hdb__put(context, db, flags, key, value);
             krb5_data_free(&key);
+            if (code == HDB_ERR_EXISTS)
+                /*
+                 * Assuming hdb_check_aliases() was called, this must be a
+                 * duplicate in the alias list.
+                 */
+                code = 0;
         }
        krb5_data_free(&value);
        if (code)
@@ -750,6 +856,10 @@ derive_keys_for_kr(krb5_context context,
      * (t - krp->epoch < 0) is better than (krp->epoch < t), making us more
      * tolerant of signed 32-bit time_t here near 2038.  Of course, we have
      * signed 32-bit time_t dragons elsewhere.
+     *
+     * We don't need to check for n == 0 && rotation_period_offset < 0 because
+     * only derive_keys_for_current_kr() calls us with non-zero rotation period
+     * offsets, and it will never call us in that case.
      */
     if (t - krp->epoch < 0)
         return 0; /* This KR is not relevant yet */
@@ -758,6 +868,37 @@ derive_keys_for_kr(krb5_context context,
     set_time = krp->epoch + krp->period * n;
     kvno = krp->base_kvno + n;
 
+    /*
+     * Since this principal is virtual, or has virtual keys, we're going to
+     * derive a "password expiration time" for it in order to help httpkadmind
+     * and other tools figure out when to request keys again.
+     *
+     * The kadm5 representation of principals does not include the set_time of
+     * keys/keysets, so we can't have httpkadmind derive a Cache-Control from
+     * that without adding yet another "TL data".  Since adding TL data is a
+     * huge pain, we'll just use the `pw_end' field of `HDB_entry' to
+     * communicate when this principal's keys will change next.
+     */
+    if (h->pw_end[0] == 0) {
+        KerberosTime used = (t - krp->epoch) % krp->period;
+        KerberosTime left = krp->period - used;
+
+        /*
+         * If `h->pw_end[0]' == 0 then this must be the current period of the
+         * current KR we're deriving keys for.  See upstairs.
+         *
+         * If there's more than a quarter of this time period left, then we'll
+         * set `h->pw_end[0]' to one quarter before the end of this time
+         * period.  Else we'll set it to 1/4 after (we'll be including the next
+         * set of derived keys, so there's no harm in waiting that long to
+         * refetch).
+         */
+        if (left > krp->period >> 2)
+            h->pw_end[0] = set_time + krp->period - (krp->period >> 2);
+        else
+            h->pw_end[0] = set_time + krp->period + (krp->period >> 2);
+    }
+
 
     /*
      * Do not waste cycles computing keys not wanted or needed.
@@ -1092,6 +1233,11 @@ derive_keys(krb5_context context,
                                "because last key rotation period "
                                "marks deletion", p);
 
+    /* See `derive_keys_for_kr()' */
+    if (h->pw_end == NULL &&
+        (h->pw_end = calloc(1, sizeof(h->pw_end[0]))) == NULL)
+        ret = krb5_enomem(context);
+
     /*
      * Derive and set in `h' its current kvno and current keys.
      *
@@ -1140,6 +1286,12 @@ derive_keys(krb5_context context,
     if (ret == 0 && *h->max_life > kr.val[current_kr].period >> 1)
         *h->max_life = kr.val[current_kr].period >> 1;
 
+    if (ret == 0 && h->pw_end[0] == 0)
+        /* Shouldn't happen */
+        h->pw_end[0] = kr.val[current_kr].epoch +
+            kr.val[current_kr].period *
+            (1 + (t - kr.val[current_kr].epoch) / kr.val[current_kr].period);
+
     free_HDB_Ext_KeyRotation(&kr);
     free_HDB_Ext_KeySet(&base_keys);
     free(p);
@@ -1506,6 +1658,10 @@ fetch_it(krb5_context context,
          * of labels.
          */
        ret = db->hdb_fetch_kvno(context, db, tmpprinc, flags, kvno, ent);
+        if (ret == 0 && nsprinc && ent->flags.invalid) {
+            free_HDB_entry(ent);
+            ret = HDB_ERR_NOENTRY;
+        }
        if (ret != HDB_ERR_NOENTRY || hdots == 0 || hdots < mindots || !tmp ||
             !do_search)
             break;
@@ -1561,7 +1717,9 @@ fetch_it(krb5_context context,
      * If unencrypted keys were requested, derive them.  There may not be any
      * key derivation to do, but that's decided in derive_keys().
      */
-    if (ret == 0) {
+    if (ret == 0 || ret == HDB_ERR_WRONG_REALM) {
+        krb5_error_code save_ret = ret;
+
         /* Fix the principal name if namespaced */
         ret = fix_princ_name(context, princ, nsprinc, ent);
 
@@ -1572,6 +1730,8 @@ fetch_it(krb5_context context,
         /* Pick the best kvno for this principal at the given time */
         if (ret == 0)
             ret = pick_kvno(context, db, flags, t, kvno, ent);
+        if (ret == 0)
+            ret = save_ret;
     }
 
 out:
@@ -1603,7 +1763,7 @@ out:
  * @param kvno Key version number (use zero to mean "current")
  * @param h Output HDB entry
  *
- * @return Zero on success, an error code otherwise.
+ * @return Zero or HDB_ERR_WRONG_REALM on success, an error code otherwise.
  */
 krb5_error_code
 hdb_fetch_kvno(krb5_context context,
@@ -1615,24 +1775,25 @@ hdb_fetch_kvno(krb5_context context,
                krb5uint32 kvno,
                hdb_entry *h)
 {
-    krb5_error_code ret = HDB_ERR_NOENTRY;
+    krb5_error_code ret;
+    krb5_timestamp now;
+
+    krb5_timeofday(context, &now);
 
     flags |= kvno ? HDB_F_KVNO_SPECIFIED : 0; /* XXX is this needed */
-    if (t == 0)
-        krb5_timeofday(context, &t);
-    ret = fetch_it(context, db, principal, flags, t, etype, kvno, h);
+    ret = fetch_it(context, db, principal, flags, t ? t : now, etype, kvno, h);
+    if (ret == 0 && t == 0 && h->flags.virtual &&
+        h->pw_end && h->pw_end[0] < now) {
+        /*
+         * This shouldn't happen!
+         *
+         * Do not allow h->pw_end[0] to be in the past for virtual principals
+         * outside testing.  This is just to prevent the AS/TGS from failing.
+         */
+        h->pw_end[0] = now + 3600;
+    }
     if (ret == HDB_ERR_NOENTRY)
        krb5_set_error_message(context, ret, "no such entry found in hdb");
-
-    /*
-     * This check is to support aliases in HDB; the force_canonicalize
-     * check is to allow HDB backends to support realm name canon
-     * independently of principal aliases (used by Samba).
-     */
-    if (ret == 0 && !(flags & HDB_F_ADMIN_DATA) &&
-        !h->flags.force_canonicalize &&
-        !krb5_realm_compare(context, principal, h->principal))
-            ret = HDB_ERR_WRONG_REALM;
     return ret;
 }
 
index 6aa5201eb8a5df99573ae7103b1f7d67062f379b..dd1f27453d5e28ae97aef11851b1ec02909cea41 100644 (file)
@@ -146,7 +146,7 @@ my_mdb_env_create_and_open(krb5_context context,
 {
     struct keep_it_open *p, *n;
     MDB_txn *txn = NULL;
-    unsigned int flags = MDB_NOSUBDIR;
+    unsigned int flags = MDB_NOSUBDIR | MDB_NOTLS;
     struct stat st;
     size_t mapsize = 0;
     int max_readers;
index 9eb96be73d06fe456ea93ecbfeb1bc6d16fb0ef6..abc75f742cc5b94a124193002c78822ad963c181 100644 (file)
@@ -59,6 +59,8 @@ HDBFlags ::= BIT STRING {
        force-canonicalize(30),         -- force the KDC to return the canonical
                                        -- principal irrespective of the setting
                                        -- of the canonicalize KDC option
+                                        -- (principals cannot have this flag
+                                        -- set when stored into the HDB)
        do-not-store(31)                -- Not to be modified and stored in HDB
 }
 
index 626f8c7b07ab786e73db7e41de7353a918f3b5a5..a96eb632e527b137dc5c8232cf8eac7fb59b1015 100644 (file)
@@ -3,3 +3,7 @@
 --sequence=HDB-Ext-KeySet
 --sequence=Keys
 --decorate=HDB_entry:void:context?:::
+# The `aliased` field is for indicating whether a name used in an HDB
+# lookup was an alias.  This could be useful to applications when hard
+# aliasing is implemented in an HDB backend, as it should be.
+--decorate=HDB_entry:int:aliased:::
index b1aa0207c9780dce7f5faa85bd4c5be3561b1cbc..df16cb782faa797d44aa7ac2b1538d8a400e5cc5 100644 (file)
@@ -223,10 +223,9 @@ hdb_get_entry(krb5_context context,
                          HDB_F_GET_CLIENT|HDB_F_GET_SERVER|HDB_F_GET_KRBTGT,
                          0, 0, kvno, &ent);
 
-    if(ret == HDB_ERR_NOENTRY) {
+    if (ret == HDB_ERR_WRONG_REALM || ret == HDB_ERR_NOENTRY)
        ret = KRB5_KT_NOTFOUND;
-       goto out;
-    }else if(ret)
+    if (ret)
        goto out;
 
     if(kvno && (krb5_kvno)ent.kvno != kvno) {
index 33805b8ed1a781081ee08756a462804f464db37b..3dda886edc5d69776dbc64d36b609d3220da4385 100644 (file)
@@ -2443,7 +2443,7 @@ hx509_verify_path(hx509_context context,
                type = EE_CERT;
            }
        }
-        fallthrough;
+        HEIM_FALLTHROUGH;
        case EE_CERT:
            /*
             * If there where any proxy certificates in the chain
index d2728a38c2f9f20b10503a523a65878b3df964e5..c770b8132624aa9acbec3523776cc63ef8ca8e73 100644 (file)
@@ -182,7 +182,7 @@ fill_CMSIdentifier(const hx509_cert cert,
                                                   &id->u.subjectKeyIdentifier);
        if (ret == 0)
            break;
-        fallthrough;
+        HEIM_FALLTHROUGH;
     case CMS_ID_NAME: {
        hx509_name name;
 
index a22f6252cfa7a824bd31ffce8d318f64a4d15fd2..e1fbe7c06886636fcfe70001c587351ebfd47214 100644 (file)
@@ -230,7 +230,7 @@ hx509_pem_read(hx509_context context,
                where = INDATA;
                goto indata;
            }
-            fallthrough;
+            HEIM_FALLTHROUGH;
        case INHEADER:
            if (buf[0] == '\0') {
                where = INDATA;
diff --git a/third_party/heimdal/lib/hx509/hxtool.1 b/third_party/heimdal/lib/hx509/hxtool.1
new file mode 100644 (file)
index 0000000..3775007
--- /dev/null
@@ -0,0 +1,207 @@
+.\" Copyright (c) 2022 Kungliga Tekniska Högskolan
+.\" (Royal Institute of Technology, Stockholm, Sweden).
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\"
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\"
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" 3. Neither the name of the Institute nor the names of its contributors
+.\"    may be used to endorse or promote products derived from this software
+.\"    without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $Id$
+.\"
+.Dd February 22, 2022
+.Dt HXTOOL 1
+.Os HEIMDAL
+.Sh NAME
+.Nm hxtool
+.Nd PKIX command-line utility
+.Sh SYNOPSIS
+.Nm
+.Bk -words
+.Oo Fl Fl version Oc
+.Oo Fl Fl help Oc
+.Op Ar sub-command
+.Ek
+.Sh DESCRIPTION
+.Nm
+is a utility for making certificate sigining requests (CSRs),
+displaying CSRs, signing certificates, etc.
+are given, then the value will be parsed and displayed using just
+the self-describing nature of DER.
+.Pp
+All sub-commands have their own help message, shown when invoked
+with the
+.Fl Fl help
+or
+.Fl h
+option.
+.Pp
+Supported commands:
+.Bl -tag -width Ds -offset indent
+.It help
+.It list-oids
+.It verify
+.It print
+.It validate
+.It certificate-copy, cc
+.It ocsp-fetch
+.It ocsp-verify
+.It ocsp-print
+.It revoke-print
+.It generate-key
+.It request-create
+.It request-print
+.It query
+.It info
+.It random-data
+.It crypto-available
+.It crypto-select
+.It hex
+.It certificate-sign, cert-sign, issue-certificate, ca
+.It crl-sign
+.El
+Other sub-commands reported by the
+.Nm help
+sub-command are not stable or fully supported at this time.
+.Sh CERTIFICATE STORES
+Stores of certificates and/or keys have string names that can be
+used with
+.Nm 's
+commands.
+Sub-commands use these certificate store names to refer to files
+and tokens where keys and/or certificates are to be found or
+written.
+For example,
+.Sq FILE:/path/to/some/file .
+.Pp
+Use the
+.Nm certificate-copy
+command to copy certificates from one store to another.
+This is useful for, e.g., converting DER files to PEM or
+vice-versa.
+.Pp
+Heimdal supports a variety of certificate and key store types:
+.Bl -tag -width Ds -offset indent
+.It PEM-FILE:/path
+If writing, PEM will be written.
+If reading, PEM will be expected.
+.It DER-FILE:/path
+If writing, DER will be written.
+If reading, DER will be expected.
+.It FILE:/path
+If writing, PEM will be written.
+If reading, PEM or DER will be detected.
+.It PKCS12:/path
+Barely supported at this time.
+.It DIR:/path
+OpenSSL-style hashed directory of trust anchors.
+.It MEMORY:name
+An in-memory only store, usually never used in
+.NM 's
+commands.
+.It KEYCHAIN:system-anchors
+On OS X this refers to the system's trust anchors.
+.It KEYCHAIN:FILE:/path
+On OS X this refers to an OS X keychain at the given path.
+.It NULL:
+An empty store.
+.It PKCS11:/path/to/shared/object,slot=NUMBER
+Loads the given PKCS#11 provider object and uses the token at the
+given slot number.
+.El
+.Sh CERTIFICATES
+You can validate a certificate with the
+.Nm validate
+sub-command, or verify a certificate and its certification path
+with the
+.Nm verify
+sub-command.
+.Pp
+You can display a certificate using the
+.Nm print 
+sub-command:
+.Pp
+.Nm print
+.Oo options Oc
+.Ar STORE
+.Pp
+Options:
+.Bl -tag -width Ds -offset indent
+.Op Fl Fl content
+.Op Fl Fl info
+.Op Fl Fl never-fail
+.Op Fl Fl pass=password
+.Op Fl Fl raw-json
+.El
+.Pp
+The
+.Fl Fl pass=password
+option is for PKCS#12 and PKCS#11 stores, and if needed and not
+given, will be prompted for.
+Note that it's not secure to pass passwords as command-line
+arguments on multi-tenant systems.
+.Pp
+The
+.Fl Fl raw-json
+option prints the certificate(s) in the given
+.Ar STORE
+as a JSON dump of their DER using an experimental (i.e.,
+unstable) schema.
+.Sh KEYS
+The
+.Nm generate-key
+sub-command will generate a key.
+.Sh CERTIFICATE SIGNING REQUESTS
+The
+.Nm request-create
+sub-command will create a CSR.
+The
+.Nm request-print
+sub-command will display a CSR.
+.Sh CERTIFICATE ISSUANCE / CERTIFICATION AUTHORITY
+The
+.Nm certificate-sign
+sub-command will issue a certificate.
+See its usage message.
+.Sh ONLINE CERTIFICATE STATUS PROTOCOL
+The
+.Nm ocsp-fetch
+sub-command will fetch OCSP Responses for the given
+certificates.
+.Pp
+The
+.Nm ocsp-verify
+sub-command will verify OCSP Responses.
+.Pp
+The
+.Nm ocsp-print
+sub-command will display OCSP Responses.
+.Sh CERTIFICATE REVOCATION LIST
+The
+.Nm crl-sign
+sub-command will add certificates to a certificate revocation
+list.
+.Sh SEE ALSO
+.Xr openssl 1
index 1bcfdfa44e9db6a3c2cdfabfbf4c1a35f4bc9cd2..aa9e279c81c6328832b2dbf2c9bc32164ca51936 100644 (file)
@@ -2076,39 +2076,33 @@ hxtool_ca(struct certificate_sign_options *opt, int argc, char **argv)
     }
 
     if (opt->generate_key_string) {
-       struct hx509_generate_private_context *keyctx;
+        /*
+         * Note that we used to set isCA in the key gen context.  Now that we
+         * use get_key() we no longer set isCA in the key gen context.  But
+         * nothing uses that field of the key gen context.
+         */
+        get_key(opt->certificate_private_key_string,
+                opt->generate_key_string,
+                opt->key_bits_integer,
+                &cert_key);
 
-       ret = _hx509_generate_private_key_init(context,
-                                              &asn1_oid_id_pkcs1_rsaEncryption,
-                                              &keyctx);
+       ret = hx509_private_key2SPKI(context, cert_key, &spki);
        if (ret)
-           hx509_err(context, 1, ret, "generate private key");
-
-       if (opt->issue_ca_flag)
-           _hx509_generate_private_key_is_ca(context, keyctx);
-
-       if (opt->key_bits_integer)
-           _hx509_generate_private_key_bits(context, keyctx,
-                                            opt->key_bits_integer);
+           errx(1, "hx509_private_key2SPKI: %d\n", ret);
 
-       ret = _hx509_generate_private_key(context, keyctx,
-                                         &cert_key);
-       _hx509_generate_private_key_free(&keyctx);
+        if (opt->self_signed_flag)
+            private_key = cert_key;
+    } else if (opt->certificate_private_key_string) {
+       ret = read_private_key(opt->certificate_private_key_string, &cert_key);
        if (ret)
-           hx509_err(context, 1, ret, "generate private key");
+           err(1, "read_private_key for certificate");
 
        ret = hx509_private_key2SPKI(context, cert_key, &spki);
        if (ret)
            errx(1, "hx509_private_key2SPKI: %d\n", ret);
 
-       if (opt->self_signed_flag)
-           private_key = cert_key;
-    }
-
-    if (opt->certificate_private_key_string) {
-       ret = read_private_key(opt->certificate_private_key_string, &cert_key);
-       if (ret)
-           err(1, "read_private_key for certificate");
+        if (opt->self_signed_flag)
+            private_key = cert_key;
     }
 
     if (opt->subject_string) {
@@ -2319,7 +2313,31 @@ hxtool_ca(struct certificate_sign_options *opt, int argc, char **argv)
            hx509_err(context, 1, ret, "hx509_ca_sign");
     }
 
-    if (cert_key) {
+    /* Copy the private key to the output store, maybe */
+    if (cert_key && opt->generate_key_string &&
+        !opt->certificate_private_key_string) {
+        /*
+         * Yes: because we're generating the key and --certificate-private-key
+         * was not given.
+         */
+       ret = _hx509_cert_assign_key(cert, cert_key);
+       if (ret)
+           hx509_err(context, 1, ret, "_hx509_cert_assign_key");
+    } else if (opt->certificate_private_key_string && opt->certificate_string &&
+               strcmp(opt->certificate_private_key_string,
+                      opt->certificate_string) == 0) {
+        /*
+         * Yes: because we're re-writing the store whence the private key.  We
+         * would lose the key otherwise.
+         */
+       ret = _hx509_cert_assign_key(cert, cert_key);
+       if (ret)
+           hx509_err(context, 1, ret, "_hx509_cert_assign_key");
+    } else if (opt->self_signed_flag && opt->ca_private_key_string &&
+               opt->certificate_string &&
+               strcmp(opt->ca_private_key_string,
+                      opt->certificate_string) == 0) {
+        /* Yes: same as preceding */
        ret = _hx509_cert_assign_key(cert, cert_key);
        if (ret)
            hx509_err(context, 1, ret, "_hx509_cert_assign_key");
index 2b3f46d532a6563a6b4f8726c7cf6dde24baaca4..c8fa42e5f99f4c02613eb07a3529b02657216536 100644 (file)
@@ -1046,7 +1046,7 @@ authorize_feat(hx509_request req, abitstring a, size_t n, int idx)
     switch (ret) {
     case 0:
         req->nauthorized++;
-        fallthrough;
+        HEIM_FALLTHROUGH;
     case -1:
         return 0;
     default:
@@ -1063,7 +1063,7 @@ reject_feat(hx509_request req, abitstring a, size_t n, int idx)
     switch (ret) {
     case 0:
         req->nauthorized--;
-        fallthrough;
+        HEIM_FALLTHROUGH;
     case -1:
         return 0;
     default:
@@ -1245,7 +1245,7 @@ san_map_type(GeneralName *san)
             if (der_heim_oid_cmp(&san->u.otherName.type_id, map[i].oid) == 0)
                 return map[i].type;
     }
-        fallthrough;
+        HEIM_FALLTHROUGH;
     default:                               return HX509_SAN_TYPE_UNSUPPORTED;
     }
 }
@@ -1360,7 +1360,7 @@ hx509_request_get_san(hx509_request req,
     case HX509_SAN_TYPE_REGISTERED_ID:
         return der_print_heim_oid(&san->u.registeredID, '.', out);
     case HX509_SAN_TYPE_XMPP:
-        fallthrough;
+        HEIM_FALLTHROUGH;
     case HX509_SAN_TYPE_MS_UPN: {
         int ret;
 
index 40601b9744fad01ba5a725d3cd9180c44380fd91..b0b2fa1cb66d8c01cfaceef317ffb69a6128ccd8 100644 (file)
@@ -122,26 +122,19 @@ mach_complete_sync(heim_sipc_call ctx, int returnvalue, heim_idata *reply)
 {
     struct mach_call_ctx *s = (struct mach_call_ctx *)ctx;
     heim_ipc_message_inband_t replyin;
-    mach_msg_type_number_t replyinCnt;
-    heim_ipc_message_outband_t replyout;
-    mach_msg_type_number_t replyoutCnt;
-    kern_return_t kr;
+    mach_msg_type_number_t replyinCnt = 0;
+    heim_ipc_message_outband_t replyout = 0;
+    mach_msg_type_number_t replyoutCnt = 0;
 
     if (returnvalue) {
        /* on error, no reply */
-       replyinCnt = 0;
-       replyout = 0; replyoutCnt = 0;
-       kr = KERN_SUCCESS;
     } else if (reply->length < 2048) {
        replyinCnt = reply->length;
        memcpy(replyin, reply->data, replyinCnt);
-       replyout = 0; replyoutCnt = 0;
-       kr = KERN_SUCCESS;
     } else {
-       replyinCnt = 0;
-       kr = vm_read(mach_task_self(),
-                    (vm_address_t)reply->data, reply->length,
-                    (vm_address_t *)&replyout, &replyoutCnt);
+       vm_read(mach_task_self(),
+               (vm_address_t)reply->data, reply->length,
+               (vm_address_t *)&replyout, &replyoutCnt);
     }
 
     mheim_ripc_call_reply(s->reply_port, returnvalue,
@@ -159,31 +152,24 @@ mach_complete_async(heim_sipc_call ctx, int returnvalue, heim_idata *reply)
 {
     struct mach_call_ctx *s = (struct mach_call_ctx *)ctx;
     heim_ipc_message_inband_t replyin;
-    mach_msg_type_number_t replyinCnt;
-    heim_ipc_message_outband_t replyout;
-    mach_msg_type_number_t replyoutCnt;
-    kern_return_t kr;
+    mach_msg_type_number_t replyinCnt = 0;
+    heim_ipc_message_outband_t replyout = 0;
+    mach_msg_type_number_t replyoutCnt = 0;
 
     if (returnvalue) {
        /* on error, no reply */
-       replyinCnt = 0;
-       replyout = 0; replyoutCnt = 0;
-       kr = KERN_SUCCESS;
     } else if (reply->length < 2048) {
        replyinCnt = reply->length;
        memcpy(replyin, reply->data, replyinCnt);
-       replyout = 0; replyoutCnt = 0;
-       kr = KERN_SUCCESS;
     } else {
-       replyinCnt = 0;
-       kr = vm_read(mach_task_self(),
-                    (vm_address_t)reply->data, reply->length,
-                    (vm_address_t *)&replyout, &replyoutCnt);
+       vm_read(mach_task_self(),
+               (vm_address_t)reply->data, reply->length,
+               (vm_address_t *)&replyout, &replyoutCnt);
     }
 
-    kr = mheim_aipc_acall_reply(s->reply_port, returnvalue,
-                               replyin, replyinCnt,
-                               replyout, replyoutCnt);
+    mheim_aipc_acall_reply(s->reply_port, returnvalue,
+                          replyin, replyinCnt,
+                          replyout, replyoutCnt);
     heim_ipc_free_cred(s->cred);
     free(s->req.data);
     free(s);
@@ -700,6 +686,7 @@ maybe_close(struct client *c)
     dispatch_release(c->out);
 #endif
     close(c->fd); /* ref count fd close */
+    free(c->inmsg);
     free(c);
     return 1;
 }
@@ -1382,4 +1369,3 @@ heim_ipc_main(void)
     process_loop();
 #endif
 }
-
index 58ccf32eacdde035c903a9b50c95bad2797cfc38..b9b9c9023b933c695e104f73f10be08d3aac4c5a 100644 (file)
@@ -1065,6 +1065,33 @@ kadm5_ad_get_principals(void *server_handle,
 #endif
 }
 
+static kadm5_ret_t
+kadm5_ad_iter_principals(void *server_handle,
+                        const char *expression,
+                        int (*cb)(void *, const char *),
+                        void *cbdata)
+{
+    kadm5_ad_context *context = server_handle;
+
+#ifdef OPENLDAP
+    kadm5_ret_t ret;
+
+    ret = ad_get_cred(context, NULL);
+    if (ret)
+       return ret;
+
+    ret = _kadm5_ad_connect(server_handle);
+    if (ret)
+       return ret;
+
+    krb5_set_error_message(context->context, KADM5_RPC_ERROR, "Function not implemented");
+    return KADM5_RPC_ERROR;
+#else
+    krb5_set_error_message(context->context, KADM5_RPC_ERROR, "Function not implemented");
+    return KADM5_RPC_ERROR;
+#endif
+}
+
 static kadm5_ret_t
 kadm5_ad_get_privs(void *server_handle, uint32_t*privs)
 {
@@ -1169,7 +1196,7 @@ kadm5_ad_modify_principal(void *server_handle,
        if (entry->attributes & KRB5_KDB_REQUIRES_HW_AUTH)
            i |= UF_SMARTCARD_REQUIRED;
        else
-           i &= UF_SMARTCARD_REQUIRED;
+           i &= ~UF_SMARTCARD_REQUIRED;
        if (entry->attributes & KRB5_KDB_DISALLOW_SVR)
            i &= ~UF_WORKSTATION_TRUST_ACCOUNT;
        else
@@ -1388,6 +1415,9 @@ set_funcs(kadm5_ad_context *c)
     SET(c, lock);
     SET(c, unlock);
     SETNOTIMP(c, setkey_principal_3);
+    SETNOTIMP(c, prune_principal);
+    SET(c, iter_principals);
+    SET(c, dup_context);
 }
 
 kadm5_ret_t
@@ -1455,6 +1485,12 @@ kadm5_ad_init_with_password_ctx(krb5_context context,
     return 0;
 }
 
+kadm5_ret_t
+kadm5_ad_dup_context(void *in, void **out)
+{
+    return ENOTSUP;
+}
+
 kadm5_ret_t
 kadm5_ad_init_with_password(const char *client_name,
                            const char *password,
index 9993b0a349eed5fcbd280dea7c1ae478e6e17290..210bf93b91cc3404726bcef588755c89e0c886ba 100644 (file)
@@ -38,6 +38,12 @@ RCSID("$Id$");
 #define __CALL(F, P) (*((kadm5_common_context*)server_handle)->funcs.F)P
 #define __CALLABLE(F) (((kadm5_common_context*)server_handle)->funcs.F != 0)
 
+kadm5_ret_t
+kadm5_dup_context(void *server_handle, void **dup_server_handle)
+{
+    return __CALL(dup_context, (server_handle, dup_server_handle));
+}
+
 kadm5_ret_t
 kadm5_chpass_principal(void *server_handle,
                       krb5_principal princ,
@@ -222,6 +228,15 @@ kadm5_get_principals(void *server_handle,
     return __CALL(get_principals, (server_handle, expression, princs, count));
 }
 
+kadm5_ret_t
+kadm5_iter_principals(void *server_handle,
+                     const char *expression,
+                      int (*cb)(void *, const char *),
+                      void *cbdata)
+{
+    return __CALL(iter_principals, (server_handle, expression, cb, cbdata));
+}
+
 kadm5_ret_t
 kadm5_get_privs(void *server_handle,
                uint32_t *privs)
index 0c154ecfef08e92aae4c24f8f3b3279f3213a14c..5c9b3e31c81a174c6b6857b76e326f510cd1a626 100644 (file)
@@ -112,6 +112,8 @@ set_funcs(kadm5_server_context *c)
     SET(c, lock);
     SET(c, unlock);
     SET(c, setkey_principal_3);
+    SET(c, iter_principals);
+    SET(c, dup_context);
 }
 
 #ifndef NO_UNIX_SOCKETS
@@ -250,6 +252,8 @@ _kadm5_s_init_context(kadm5_server_context **ctx,
     krb5_add_et_list (context, initialize_kadm5_error_table_r);
 
 #define is_set(M) (params && params->mask & KADM5_CONFIG_ ## M)
+    if (params)
+        (*ctx)->config.mask = params->mask;
     if (is_set(REALM)) {
        (*ctx)->config.realm = strdup(params->realm);
         if ((*ctx)->config.realm == NULL)
@@ -275,9 +279,9 @@ _kadm5_s_init_context(kadm5_server_context **ctx,
            return krb5_enomem(context);
     }
 
-    find_db_spec(*ctx);
-
-    ret = _kadm5_s_init_hooks(*ctx);
+    ret = find_db_spec(*ctx);
+    if (ret == 0)
+        ret = _kadm5_s_init_hooks(*ctx);
     if (ret != 0) {
        kadm5_s_destroy(*ctx);
        *ctx = NULL;
index 1c2ab15f30dbab5917857ca5143d8b8ffe86815b..e603497ace2edc2b3e110492e4b40db571c786c7 100644 (file)
@@ -50,6 +50,14 @@ get_default(kadm5_server_context *context, krb5_principal princ,
     ret = kadm5_s_get_principal(context, def_principal, def,
                                KADM5_PRINCIPAL_NORMAL_MASK);
     krb5_free_principal (context->context, def_principal);
+
+    if (ret) {
+        /* Copy defaults from kadmin/init.c */
+        memset(def, 0, sizeof(*def));
+        def->max_life = 24 * 60 * 60;
+        def->max_renewable_life = 7 * def->max_life;
+        def->attributes = KRB5_KDB_DISALLOW_ALL_TIX;
+    }
     return ret;
 }
 
index fc86e5960e6daacea43e8779fb9b012a4ab265c5..0558b45c57752f7714793aacd56b58e68067ed59 100644 (file)
@@ -42,10 +42,12 @@ RCSID("$Id$");
 static void
 destroy_config (kadm5_config_params *c)
 {
-    free (c->realm);
-    free (c->dbname);
-    free (c->acl_file);
-    free (c->stash_file);
+    if (!c)
+        return;
+    free(c->realm);
+    free(c->dbname);
+    free(c->acl_file);
+    free(c->stash_file);
 }
 
 /*
@@ -55,6 +57,8 @@ destroy_config (kadm5_config_params *c)
 static void
 destroy_kadm5_log_context (kadm5_log_context *c)
 {
+    if (!c)
+        return;
     free(c->log_file);
     if (c->socket_fd != rk_INVALID_SOCKET)
         rk_closesocket(c->socket_fd);
index 5b2bcca762a4b5b8e853c50903ecf68faf2ae232..1c1fd9dce80146c611fa3069858f0b5f190c3c0e 100644 (file)
@@ -89,7 +89,7 @@ kadm5_c_get_principal(void *server_handle,
 
   out_keep_error:
     if (ret == 0)
-       kadm5_ret_principal_ent(sp, ent);
+       ret = kadm5_ret_principal_ent(sp, ent);
     krb5_storage_free(sp);
     krb5_data_free(&reply);
     return ret;
index 4998223d2605a07bdf50cf3994b0e476797009d0..93d7ce7ed1b2f64db545c63944bf5cbab03f02aa 100644 (file)
@@ -66,7 +66,7 @@ kadm5_c_get_principals(void *server_handle,
     ret = krb5_store_int32(sp, kadm_get_princs);
     if (ret)
        goto out;
-    ret = krb5_store_int32(sp, expression != NULL);
+    ret = krb5_store_int32(sp, expression != NULL ? 1 : 0);
     if (ret)
        goto out;
     if (expression) {
@@ -117,3 +117,187 @@ kadm5_c_get_principals(void *server_handle,
     krb5_data_free(&reply);
     return ret;
 }
+
+kadm5_ret_t
+kadm5_c_iter_principals(void *server_handle,
+                       const char *expression,
+                       int (*cb)(void *, const char *),
+                       void *cbdata)
+{
+    kadm5_client_context *context = server_handle;
+    kadm5_ret_t ret;
+    krb5_storage *sp;
+    unsigned char buf[1024];
+    int32_t tmp;
+    krb5_data reply;
+    size_t i;
+
+    ret = _kadm5_connect(server_handle, 0 /* want_write */);
+    if (ret)
+       return ret;
+
+    krb5_data_zero(&reply);
+
+    sp = krb5_storage_from_mem(buf, sizeof(buf));
+    if (sp == NULL) {
+       ret = krb5_enomem(context->context);
+       goto out_keep_error;
+    }
+    ret = krb5_store_int32(sp, kadm_get_princs);
+    if (ret)
+       goto out;
+
+    /*
+     * Our protocol has an int boolean for this operation to indicate whether
+     * there's an expression.  What we'll do here is that instead of sending
+     * just false or trueish, for online iteration we'll send a number other
+     * than 0 or 1 -- a magic value > 0 and < INT_MAX.
+     *
+     * In response we'll expect multiple replies, each with up to some small
+     * number of principal names.  See kadmin/server.c.
+     */
+    ret = krb5_store_int32(sp, 0x55555555);
+    if (ret)
+       goto out;
+    ret = krb5_store_string(sp, expression ? expression : "");
+    if (ret)
+        goto out;
+    ret = _kadm5_client_send(context, sp);
+    if (ret)
+       goto out_keep_error;
+    ret = _kadm5_client_recv(context, &reply);
+    if (ret)
+       goto out_keep_error;
+    krb5_storage_free(sp);
+    sp = krb5_storage_from_data (&reply);
+    if (sp == NULL) {
+       ret = krb5_enomem(context->context);
+       goto out_keep_error;
+    }
+    ret = krb5_ret_int32(sp, &tmp);
+    if (ret == 0)
+       ret = tmp;
+
+    if (ret)
+       goto out;
+
+    ret = krb5_ret_int32(sp, &tmp);
+    if (ret)
+       goto out;
+
+    if (tmp < 0) {
+        size_t n = -tmp;
+        int more = 1;
+        int stop = 0;
+
+        /* The server supports online iteration, hooray! */
+
+        while (more) {
+            /*
+             * We expect any number of chunks, each having `n' names, except
+             * the last one would have fewer than `n' (possibly zero, even).
+             *
+             * After that we expect one more reply with just a final return
+             * code.
+             */
+            krb5_data_free(&reply);
+            krb5_storage_free(sp);
+            sp = NULL;
+            ret = _kadm5_client_recv(context, &reply);
+            if (ret == 0 && (sp = krb5_storage_from_data(&reply)) == NULL)
+                ret = krb5_enomem(context->context);
+            if (ret)
+                goto out;
+
+            /* Every chunk begins with a status code */
+            ret = krb5_ret_int32(sp, &tmp);
+            if (ret == 0)
+                ret = tmp;
+            if (ret)
+                goto out;
+
+            /* We expect up to -tmp principals per reply */
+            for (i = 0; i < n; i++) {
+                char *princ = NULL;
+
+                ret = krb5_ret_string(sp, &princ);
+                if (ret == HEIM_ERR_EOF) {
+                    /* This was the last reply */
+                    more = 0;
+                    ret = 0;
+                    break;
+                }
+                if (ret)
+                    goto out;
+                if (!stop) {
+                    stop = cb(cbdata, princ);
+                    if (stop) {
+                        /*
+                         * Tell the server to stop.
+                         *
+                         * We use a NOP for this, but with a payload that says
+                         * "don't reply to the NOP" just in case the NOP
+                         * arrives and is processed _after_ the LISTing has
+                         * finished.
+                         */
+                        krb5_storage_free(sp);
+                        if ((sp = krb5_storage_emem()) &&
+                            krb5_store_int32(sp, kadm_nop) == 0 &&
+                            krb5_store_int32(sp, 0))
+                            (void) _kadm5_client_send(context, sp);
+                    }
+                }
+                free(princ);
+            }
+
+            if (!more) {
+                if (ret == 0)
+                    ret = stop;
+                break;
+            }
+        }
+        /* Get the final result code */
+        krb5_data_free(&reply);
+        krb5_storage_free(sp);
+        sp = NULL;
+        ret = _kadm5_client_recv(context, &reply);
+        if (ret == 0 && (sp = krb5_storage_from_data(&reply)) == NULL)
+            ret = krb5_enomem(context->context);
+        if (ret)
+            goto out;
+        ret = krb5_ret_int32(sp, &tmp);
+        if (ret == 0)
+            ret = tmp;
+        if (!stop) {
+            /*
+             * Send our "interrupt" after the last chunk if we hand't
+             * interrupted already.
+             */
+            krb5_storage_free(sp);
+            if ((sp = krb5_storage_emem()) &&
+                krb5_store_int32(sp, kadm_nop) == 0)
+                (void) _kadm5_client_send(context, sp);
+        }
+    } else {
+        size_t n = tmp;
+
+        /* Old server -- listing not online */
+        for (i = 0; i < n; i++) {
+            char *princ = NULL;
+
+            ret = krb5_ret_string(sp, &princ);
+            if (ret)
+                goto out;
+            cb(cbdata, princ);
+            free(princ);
+        }
+    }
+
+  out:
+    krb5_clear_error_message(context->context);
+
+  out_keep_error:
+    krb5_storage_free(sp);
+    krb5_data_free(&reply);
+    return ret;
+}
index 27fac2bbb0bce38969d2c34b99a7ebe0664fc99c..14d3907d7592b7a61d6e63842a1c000700762b7a 100644 (file)
@@ -39,17 +39,27 @@ struct foreach_data {
     const char *exp;
     char *exp2;
     char **princs;
-    int count;
+    size_t nalloced;
+    size_t count;
 };
 
 static krb5_error_code
 add_princ(krb5_context context, struct foreach_data *d, char *princ)
 {
-    char **tmp;
-    tmp = realloc(d->princs, (d->count + 1) * sizeof(*tmp));
-    if (tmp == NULL)
-       return krb5_enomem(context);
-    d->princs = tmp;
+
+    if (d->count == INT_MAX)
+        return ERANGE;
+    if (d->nalloced == d->count) {
+        size_t n = d->nalloced + (d->nalloced >> 1) + 128; /* No O(N^2) pls */
+        char **tmp;
+
+        if (SIZE_MAX / sizeof(*tmp) <= n)
+            return ERANGE;
+        if ((tmp = realloc(d->princs, n * sizeof(*tmp))) == NULL)
+            return krb5_enomem(context);
+        d->princs = tmp;
+        d->nalloced = n;
+    }
     d->princs[d->count++] = princ;
     return 0;
 }
@@ -84,7 +94,7 @@ kadm5_s_get_principals(void *server_handle,
 {
     struct foreach_data d;
     kadm5_server_context *context = server_handle;
-    kadm5_ret_t ret;
+    kadm5_ret_t ret = 0;
 
     if (!context->keep_open) {
        ret = context->db->hdb_open(context->context, context->db, O_RDONLY, 0);
@@ -94,33 +104,103 @@ kadm5_s_get_principals(void *server_handle,
        }
     }
     d.exp = expression;
-    {
+    d.exp2 = NULL;
+    if (expression) {
        krb5_realm r;
        int aret;
 
        ret = krb5_get_default_realm(context->context, &r);
-        if (ret)
-            goto out;
-       aret = asprintf(&d.exp2, "%s@%s", expression, r);
-       free(r);
-       if (aret == -1 || d.exp2 == NULL) {
-           ret = krb5_enomem(context->context);
-            goto out;
-       }
+        if (ret == 0) {
+            aret = asprintf(&d.exp2, "%s@%s", expression, r);
+            free(r);
+            if (aret == -1 || d.exp2 == NULL)
+                ret = krb5_enomem(context->context);
+        }
     }
     d.princs = NULL;
+    d.nalloced = 0;
     d.count = 0;
-    ret = hdb_foreach(context->context, context->db, HDB_F_ADMIN_DATA, foreach, &d);
+    if (ret == 0)
+        ret = hdb_foreach(context->context, context->db, HDB_F_ADMIN_DATA,
+                          foreach, &d);
 
     if (ret == 0)
        ret = add_princ(context->context, &d, NULL);
-    if (ret == 0){
+    if (d.count >= INT_MAX)
+        *count = INT_MAX;
+    else
+        *count = d.count - 1;
+    if (ret == 0)
        *princs = d.princs;
-       *count = d.count - 1;
-    } else
-       kadm5_free_name_list(context, d.princs, &d.count);
+    else
+       kadm5_free_name_list(context, d.princs, count);
+    free(d.exp2);
+    if (!context->keep_open)
+       context->db->hdb_close(context->context, context->db);
+    return _kadm5_error_code(ret);
+}
+
+struct foreach_online_data {
+    const char *exp;
+    char *exp2;
+    int (*cb)(void *, const char *);
+    void *cbdata;
+};
+
+static krb5_error_code
+foreach_online(krb5_context context, HDB *db, hdb_entry *ent, void *data)
+{
+    struct foreach_online_data *d = data;
+    krb5_error_code ret;
+    char *princ = NULL;
+
+    ret = krb5_unparse_name(context, ent->principal, &princ);
+    if (ret == 0) {
+        if (!d->exp ||
+            fnmatch(d->exp, princ, 0) == 0 || fnmatch(d->exp2, princ, 0) == 0)
+            ret = d->cb(d->cbdata, princ);
+        free(princ);
+    }
+    return ret;
+}
+
+kadm5_ret_t
+kadm5_s_iter_principals(void *server_handle,
+                       const char *expression,
+                       int (*cb)(void *, const char *),
+                       void *cbdata)
+{
+    struct foreach_online_data d;
+    kadm5_server_context *context = server_handle;
+    kadm5_ret_t ret = 0;
+
+    if (!context->keep_open) {
+       ret = context->db->hdb_open(context->context, context->db, O_RDONLY, 0);
+       if (ret) {
+           krb5_warn(context->context, ret, "opening database");
+           return ret;
+       }
+    }
+    d.exp = expression;
+    d.exp2 = NULL;
+    d.cb = cb;
+    d.cbdata = cbdata;
+    if (expression) {
+       krb5_realm r;
+       int aret;
+
+       ret = krb5_get_default_realm(context->context, &r);
+        if (ret == 0) {
+            aret = asprintf(&d.exp2, "%s@%s", expression, r);
+            free(r);
+            if (aret == -1 || d.exp2 == NULL)
+                ret = krb5_enomem(context->context);
+        }
+    }
+    if (ret == 0)
+        ret = hdb_foreach(context->context, context->db, HDB_F_ADMIN_DATA,
+                          foreach_online, &d);
     free(d.exp2);
- out:
     if (!context->keep_open)
        context->db->hdb_close(context->context, context->db);
     return _kadm5_error_code(ret);
index 5d585d1a29514f725e83ec50b642f92bb1748ea4..ffbb639581cf36946985834157cde2fd75ef63eb 100644 (file)
@@ -78,6 +78,8 @@ set_funcs(kadm5_client_context *c)
     SET(c, lock);
     SET(c, unlock);
     SETNOTIMP(c, setkey_principal_3);
+    SET(c, iter_principals);
+    SET(c, dup_context);
 }
 
 kadm5_ret_t
@@ -192,6 +194,56 @@ _kadm5_c_init_context(kadm5_client_context **ctx,
     return 0;
 }
 
+kadm5_ret_t
+kadm5_c_dup_context(void *vin, void **out)
+{
+    krb5_error_code ret;
+    kadm5_client_context *in = vin;
+    krb5_context context = in->context;
+    kadm5_client_context *ctx;
+
+    *out = NULL;
+    ctx = malloc(sizeof(*ctx));
+    if (ctx == NULL)
+       return krb5_enomem(in->context);
+
+
+    memset(ctx, 0, sizeof(*ctx));
+    set_funcs(ctx);
+    ctx->readonly_kadmind_port = in->readonly_kadmind_port;
+    ctx->kadmind_port = in->kadmind_port;
+
+    ret = krb5_copy_context(context, &(ctx->context));
+    if (ret == 0) {
+        ctx->my_context = TRUE;
+        ret = krb5_add_et_list(ctx->context, initialize_kadm5_error_table_r);
+    }
+    if (ret == 0 && (ctx->realm = strdup(in->realm)) == NULL)
+        ret = krb5_enomem(context);
+    if (ret == 0 &&
+        (ctx->admin_server = strdup(in->admin_server)) == NULL)
+        ret = krb5_enomem(context);
+    if (in->readonly_admin_server &&
+       (ctx->readonly_admin_server = strdup(in->readonly_admin_server)) == NULL)
+        ret = krb5_enomem(context);
+    if (in->keytab && (ctx->keytab = strdup(in->keytab)) == NULL)
+        ret = krb5_enomem(context);
+    if (in->ccache) {
+        char *fullname = NULL;
+
+        ret = krb5_cc_get_full_name(context, in->ccache, &fullname);
+        if (ret == 0)
+            ret = krb5_cc_resolve(context, fullname, &ctx->ccache);
+        free(fullname);
+    }
+    ctx->sock = -1;
+    if (ret == 0)
+        *out = ctx;
+    else
+        kadm5_c_destroy(ctx);
+    return ret;
+}
+
 static krb5_error_code
 get_kadm_ticket(krb5_context context,
                krb5_ccache id,
index 1b1d7f2ff58c0043fd5826977975a75b8f2bfadf..35402d88a52782ce6e9d04447f266383990cbfa0 100644 (file)
@@ -108,6 +108,21 @@ kadm5_s_init_with_context(krb5_context context,
     return ret;
 }
 
+kadm5_ret_t
+kadm5_s_dup_context(void *vin, void **out)
+{
+    kadm5_server_context *in = vin;
+    kadm5_ret_t ret;
+    char *p = NULL;
+
+    ret = krb5_unparse_name(in->context, in->caller, &p);
+    if (ret == 0)
+        ret = kadm5_s_init_with_context(in->context, p, NULL,
+                                        &in->config, 0, 0, out);
+    free(p);
+    return ret;
+}
+
 kadm5_ret_t
 kadm5_s_init_with_password_ctx(krb5_context context,
                               const char *client_name,
index b881c441a1a21d056f11beef3eda06d7bc5890b5..41aac0618290bf147458e4eddef5fa12e7fe9689 100644 (file)
@@ -68,6 +68,8 @@
 .Oo Fl r Ar string \*(Ba Xo Fl Fl realm= Ns Ar string Xc Oc
 .Oo Fl d Ar file \*(Ba Xo Fl Fl database= Ns Ar file Xc Oc
 .Oo Fl k Ar kspec \*(Ba Xo Fl Fl keytab= Ns Ar kspec Xc Oc
+.Oo Xo Fl Fl no-keytab Xc Oc
+.Oo Xo Fl Fl cache= Ns Ar cspec Xc Oc
 .Op Fl Fl statusfile= Ns Ar file
 .Op Fl Fl hostname= Ns Ar hostname
 .Op Fl Fl port= Ns Ar port
@@ -125,10 +127,40 @@ This should normally be defined as
 in
 .Pa /etc/services
 or another source of the services database.
-The master and slaves
-must each have access to a keytab with keys for the
-.Nm iprop
-service principal on the local host.
+.Pp
+The
+.Nm ipropd-master
+and
+.Nm ipropd-slave
+programs require acceptor and initiator credentials,
+respectively, for host-based principals for the
+.Ar iprop
+service and the fully-qualified hostnames of the hosts on which
+they run.
+.Pp
+The
+.Nm ipropd-master
+program uses, by default, the HDB-backed keytab
+.Ar HDBGET: ,
+though a file-based keytab can also be specified.
+.Pp
+The
+.Nm ipropd-slave
+program uses the default keytab, which is specified by the
+.Ev KRB5_KTNAME
+environment variable, or the value of the
+.Ar default_keytab_name
+configuration parameter in the
+.Ar [libdefaults]
+section.
+However, if the
+.Fl Fl no-keytab
+option is given, then
+.Nm ipropd-slave
+will use the given or default credentials cache, and it will
+expect that cache to be kept fresh externally (such as by the
+.Nm kinit
+program).
 .Pp
 There is a keep-alive feature logged in the master's
 .Pa slave-stats
@@ -150,6 +182,11 @@ Supported options for
 Keytab for authenticating
 .Nm ipropd-slave
 clients.
+.It Fl Fl cache= Ns Ar cspec
+If the keytab given is the empty string then credentials will be
+used from the default credentials cache, or from the
+.Ar cspec
+if given.
 .It Fl d Ar file , Fl Fl database= Ns Ar file
 Database (default per KDC)
 .It Fl Fl slave-stats-file= Ns Ar file
@@ -201,6 +238,7 @@ in the database directory, or in the directory named by the
 .Ev HEIM_PIDFILE_DIR
 environment variable.
 .Sh SEE ALSO
+.Xr kinit 1 ,
 .Xr krb5.conf 5 ,
 .Xr hprop 8 ,
 .Xr hpropd 8 ,
index 2b1be00ea606321c447c8820de7e0652963a7893..f92b20c85690c5fd09a2368d1fc3b0b606417d02 100644 (file)
@@ -39,6 +39,9 @@ static const char *config_name = "ipropd-slave";
 
 static int verbose;
 static int async_hdb = 0;
+static int no_keytab_flag;
+static char *ccache_str;
+static char *keytab_str;
 
 static krb5_log_facility *log_facility;
 static char five_min[] = "5 min";
@@ -115,8 +118,7 @@ connect_to_master (krb5_context context, const char *master,
 }
 
 static void
-get_creds(krb5_context context, const char *keytab_str,
-         krb5_ccache *cache, const char *serverhost)
+get_creds(krb5_context context, krb5_ccache *cache, const char *serverhost)
 {
     krb5_keytab keytab;
     krb5_principal client;
@@ -127,13 +129,33 @@ get_creds(krb5_context context, const char *keytab_str,
     char keytab_buf[256];
     int aret;
 
+    if (no_keytab_flag) {
+        /* We're using an externally refreshed ccache */
+        if (*cache == NULL) {
+            if (ccache_str == NULL)
+                ret = krb5_cc_default(context, cache);
+            else
+                ret = krb5_cc_resolve(context, ccache_str, cache);
+            if (ret)
+                krb5_err(context, 1, ret, "Could not resolve the default cache");
+        }
+        return;
+    }
+
     if (keytab_str == NULL) {
        ret = krb5_kt_default_name (context, keytab_buf, sizeof(keytab_buf));
-       if (ret)
-           krb5_err (context, 1, ret, "krb5_kt_default_name");
-       keytab_str = keytab_buf;
+       if (ret == 0) {
+            keytab_str = keytab_buf;
+        } else {
+           krb5_warn(context, ret, "Using HDBGET: as the default keytab");
+            keytab_str = "HDBGET:";
+        }
     }
 
+    if (*cache)
+        krb5_cc_destroy(context, *cache);
+    *cache = NULL;
+
     ret = krb5_kt_resolve(context, keytab_str, &keytab);
     if(ret)
        krb5_err(context, 1, ret, "%s", keytab_str);
@@ -690,7 +712,6 @@ static char *status_file;
 static char *config_file;
 static int version_flag;
 static int help_flag;
-static char *keytab_str;
 static char *port_str;
 static int detach_from_console;
 static int daemon_child = -1;
@@ -699,8 +720,12 @@ static struct getargs args[] = {
     { "config-file", 'c', arg_string, &config_file, NULL, NULL },
     { "realm", 'r', arg_string, &realm, NULL, NULL },
     { "database", 'd', arg_string, &database, "database", "file"},
+    { "no-keytab", 0, arg_flag, &no_keytab_flag,
+      "use externally refreshed cache", NULL },
+    { "ccache", 0, arg_string, &ccache_str,
+      "client credentials", "CCACHE" },
     { "keytab", 'k', arg_string, &keytab_str,
-      "keytab to get authentication from", "kspec" },
+      "client credentials keytab", "KEYTAB" },
     { "time-lost", 0, arg_string, &server_time_lost,
       "time before server is considered lost", "time" },
     { "status-file", 0, arg_string, &status_file,
@@ -740,7 +765,7 @@ main(int argc, char **argv)
     kadm5_server_context *server_context;
     kadm5_config_params conf;
     int master_fd;
-    krb5_ccache ccache;
+    krb5_ccache ccache = NULL;
     krb5_principal server;
     char **files;
     int optidx = 0;
@@ -858,7 +883,7 @@ main(int argc, char **argv)
     if (ret)
        krb5_err(context, 1, ret, "db->close");
 
-    get_creds(context, keytab_str, &ccache, master);
+    get_creds(context, &ccache, master);
 
     ret = krb5_sname_to_principal (context, master, IPROP_NAME,
                                   KRB5_NT_SRV_HST, &server);
@@ -923,8 +948,7 @@ main(int argc, char **argv)
        if (auth_context) {
            krb5_auth_con_free(context, auth_context);
            auth_context = NULL;
-           krb5_cc_destroy(context, ccache);
-           get_creds(context, keytab_str, &ccache, master);
+           get_creds(context, &ccache, master);
        }
         if (verbose)
             krb5_warnx(context, "authenticating to master");
index 72ba4174c1f5712ba1c7ce0e0e7ed12382a91016..56366ae204612836f0a87dc9f8b89d3686ca7e70 100644 (file)
@@ -15,6 +15,7 @@ EXPORTS
         kadm5_delete_policy
        kadm5_delete_principal
        kadm5_destroy
+       kadm5_dup_context
        kadm5_flush
        kadm5_free_key_data
        kadm5_free_name_list
@@ -32,6 +33,7 @@ EXPORTS
        kadm5_init_with_password_ctx
        kadm5_init_with_skey
        kadm5_init_with_skey_ctx
+       kadm5_iter_principals
         kadm5_lock
         kadm5_modify_policy
        kadm5_modify_principal
index 88c2f2823d2a5204b3f0f661b7aa3135efc4f06f..1cb8e3918f5c66492235e64c5dc79c4cbbe0633e 100644 (file)
@@ -68,6 +68,8 @@ struct kadm_func {
                                       int, krb5_key_salt_tuple *,
                                       krb5_keyblock *, int);
     kadm5_ret_t (*prune_principal) (void *, krb5_principal, int);
+    kadm5_ret_t (*iter_principals) (void*, const char*, int (*)(void *, const char *), void *);
+    kadm5_ret_t (*dup_context) (void*, void **);
 };
 
 typedef struct kadm5_hook_context {
index 4d89fabf30858c255be7855b2d1b1976107d2b9a..baae30b117316bba8e967d0a2ca9adfa47a309e2 100644 (file)
@@ -11,6 +11,7 @@ HEIMDAL_KADM5_CLIENT_1.0 {
                kadm5_c_create_principal;
                kadm5_c_delete_principal;
                kadm5_c_destroy;
+               kadm5_c_dup_context;
                kadm5_c_flush;
                kadm5_c_get_principal;
                kadm5_c_get_principals;
@@ -21,6 +22,8 @@ HEIMDAL_KADM5_CLIENT_1.0 {
                kadm5_c_init_with_password_ctx;
                kadm5_c_init_with_skey;
                kadm5_c_init_with_skey_ctx;
+               kadm5_c_iter_principals;
+               kadm5_c_get_privs;
                kadm5_c_modify_principal;
                kadm5_c_prune_principal;
                kadm5_c_randkey_principal;
@@ -30,6 +33,7 @@ HEIMDAL_KADM5_CLIENT_1.0 {
                kadm5_create_principal;
                kadm5_delete_principal;
                kadm5_destroy;
+               kadm5_dup_context;
                kadm5_flush;
                kadm5_free_key_data;
                kadm5_free_name_list;
@@ -43,6 +47,7 @@ HEIMDAL_KADM5_CLIENT_1.0 {
                kadm5_init_with_password_ctx;
                kadm5_init_with_skey;
                kadm5_init_with_skey_ctx;
+               kadm5_iter_principals;
                kadm5_modify_principal;
                kadm5_randkey_principal;
                kadm5_randkey_principal_3;
index e4b4100f33463d2ac71f2cb1dd79bc899cbd8c5b..6de88fc2217741a53fb49b8ccecd1d81fe803107 100644 (file)
@@ -16,6 +16,7 @@ HEIMDAL_KAMD5_SERVER_1.0 {
                kadm5_create_principal_3;
                kadm5_delete_principal;
                kadm5_destroy;
+               kadm5_dup_context;
                kadm5_decrypt_key;
                kadm5_delete_policy;
                kadm5_flush;
@@ -35,6 +36,7 @@ HEIMDAL_KAMD5_SERVER_1.0 {
                kadm5_init_with_password_ctx;
                kadm5_init_with_skey;
                kadm5_init_with_skey_ctx;
+               kadm5_iter_principals;
                kadm5_lock;
                kadm5_modify_principal;
                kadm5_modify_policy;
index 6f34ca63170980c958e883adb23e0b84b72b41be..f4e2f64b4078db7a9f67a70d07a631ea12eb977a 100644 (file)
@@ -96,7 +96,6 @@
 #endif
 #ifdef KRB5
 #include "crypto-headers.h"
-#include <krb5-v4compat.h>
 typedef struct credentials CREDENTIALS;
 #endif /* KRB5 */
 #ifndef NO_AFS
index 5af391ed99b5414914f1594be7cf4023ecc322f9..b542e8916b568820735e9cdf9853d34cb551f4cb 100644 (file)
@@ -209,7 +209,7 @@ _kafs_derive_des_key(krb5_enctype enctype, void *keydata, size_t keylen,
        ret = compress_parity_bits(keydata, &keylen);
        if (ret)
            return ret;
-        fallthrough;
+        HEIM_FALLTHROUGH;
     default:
        if (enctype < 0)
            return KRB5_PROG_ETYPE_NOSUPP;
index c1345c28a5ab5d41cf9ab97bc40f68620c105a44..ecce461dd89cab9f5e12377a4d21da92f2015ea5 100644 (file)
@@ -193,7 +193,6 @@ dist_libkrb5_la_SOURCES =                   \
        keytab_keyfile.c                        \
        keytab_memory.c                         \
        krb5_locl.h                             \
-       krb5-v4compat.h                         \
        krcache.c                               \
        krbhst.c                                \
        kuserok.c                               \
index 40ca0fb0bccc1bd7a76cc7dbf2e00f85b2d4d549..993e76fcc23f56dbfbadbf1f62e18bb6e2c3a3f0 100644 (file)
@@ -2,19 +2,19 @@
 #
 # Copyright (c) 2009 - 2017, Secure Endpoints Inc.
 # All rights reserved.
-# 
+#
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions
 # are met:
-# 
+#
 # - Redistributions of source code must retain the above copyright
 #   notice, this list of conditions and the following disclaimer.
-# 
+#
 # - Redistributions in binary form must reproduce the above copyright
 #   notice, this list of conditions and the following disclaimer in
 #   the documentation and/or other materials provided with the
 #   distribution.
-# 
+#
 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
@@ -27,7 +27,7 @@
 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 # POSSIBILITY OF SUCH DAMAGE.
-# 
+#
 
 RELDIR=lib\krb5
 
@@ -184,7 +184,6 @@ INCFILES=                   \
        $(INCDIR)\krb5_locl.h   \
        $(INCDIR)\krb5-protos.h \
        $(INCDIR)\krb5-private.h        \
-       $(INCDIR)\krb5-v4compat.h       \
        $(INCDIR)\crypto.h \
        $(INCDIR)\an2ln_plugin.h \
        $(INCDIR)\ccache_plugin.h \
@@ -267,7 +266,6 @@ dist_libkrb5_la_SOURCES =                   \
        keytab_keyfile.c                        \
        keytab_memory.c                         \
        krb5_locl.h                             \
-       krb5-v4compat.h                         \
        krbhst.c                                \
        kuserok.c                               \
        kx509.c                                 \
index 11f270d4978352683bc01348643ed868aee9988d..8515c306e69ea9c82984bd43aad99f136531129b 100644 (file)
@@ -420,6 +420,7 @@ an2ln_def_plug_an2ln(void *plug_ctx, krb5_context context,
        heim_dict_set_value(db_options, HSTR("read-only"),
                            heim_number_create(1));
     dbh = heim_db_create(NULL, an2ln_db_fname, db_options, &error);
+    heim_release(db_options);
     if (dbh == NULL) {
        krb5_set_error_message(context, heim_error_get_code(error),
                               N_("Couldn't open aname2lname-text-db", ""));
index 12f0b1546893e5f437304b8b119de41d3f873626..1982925bf0653ad578b36df8ad76dd49617b3dba 100644 (file)
@@ -384,7 +384,7 @@ process_reply (krb5_context context,
     ap_rep_data.data = reply + 6;
     ap_rep_data.length  = (reply[4] << 8) | (reply[5]);
 
-    if (reply + len < (u_char *)ap_rep_data.data + ap_rep_data.length) {
+    if (len - 6 < ap_rep_data.length) {
        str2data (result_string, "client: wrong AP len in reply");
        *result_code = KRB5_KPASSWD_MALFORMED;
        return 0;
index 4040a983518e77b55266a9f6a64256e08d409591..d6ddd902d646f9ed705ae213f796506ce708451e 100644 (file)
@@ -545,7 +545,7 @@ copy_etypes (krb5_context context,
 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
 krb5_copy_context(krb5_context context, krb5_context *out)
 {
-    krb5_error_code ret;
+    krb5_error_code ret = 0;
     krb5_context p;
 
     *out = NULL;
@@ -554,81 +554,80 @@ krb5_copy_context(krb5_context context, krb5_context *out)
     if (p == NULL)
        return krb5_enomem(context);
 
-    if ((p->hcontext = heim_context_init()) == NULL) {
-        ret = ENOMEM;
-        goto out;
-    }
+    p->cc_ops = NULL;
+    p->etypes = NULL;
+    p->kt_types = NULL;
+    p->cfg_etypes = NULL;
+    p->etypes_des = NULL;
+    p->default_realms = NULL;
+    p->extra_addresses = NULL;
+    p->ignore_addresses = NULL;
 
-    heim_context_set_log_utc(p->hcontext, context->log_utc);
-
-    if (context->default_cc_name &&
-       (p->default_cc_name = strdup(context->default_cc_name)) == NULL) {
+    if ((p->hcontext = heim_context_init()) == NULL)
         ret = ENOMEM;
-        goto out;
+
+    if (ret == 0) {
+        heim_context_set_log_utc(p->hcontext, context->log_utc);
+        ret = _krb5_config_copy(context, context->cf, &p->cf);
     }
-    if (context->default_cc_name_env &&
-       (p->default_cc_name_env =
-            strdup(context->default_cc_name_env)) == NULL) {
-        ret = ENOMEM;
-        goto out;
+    if (ret == 0)
+        ret = init_context_from_config_file(p);
+    if (ret == 0 && context->default_cc_name) {
+        free(p->default_cc_name);
+        if ((p->default_cc_name = strdup(context->default_cc_name)) == NULL)
+            ret = ENOMEM;
     }
-    if (context->configured_default_cc_name &&
-       (p->configured_default_cc_name =
-            strdup(context->configured_default_cc_name)) == NULL) {
-        ret = ENOMEM;
-        goto out;
+    if (ret == 0 && context->default_cc_name_env) {
+        free(p->default_cc_name_env);
+        if ((p->default_cc_name_env =
+             strdup(context->default_cc_name_env)) == NULL)
+            ret = ENOMEM;
+    }
+    if (ret == 0 && context->configured_default_cc_name) {
+        free(context->configured_default_cc_name);
+        if ((p->configured_default_cc_name =
+             strdup(context->configured_default_cc_name)) == NULL)
+            ret = ENOMEM;
     }
 
-    if (context->etypes) {
+    if (ret == 0 && context->etypes) {
+        free(p->etypes);
        ret = copy_etypes(context, context->etypes, &p->etypes);
-       if (ret)
-           goto out;
     }
-    if (context->cfg_etypes) {
+    if (ret == 0 && context->cfg_etypes) {
+        free(p->cfg_etypes);
        ret = copy_etypes(context, context->cfg_etypes, &p->cfg_etypes);
-       if (ret)
-           goto out;
     }
-    if (context->etypes_des) {
+    if (ret == 0 && context->etypes_des) {
+        free(p->etypes_des);
        ret = copy_etypes(context, context->etypes_des, &p->etypes_des);
-       if (ret)
-           goto out;
     }
 
-    if (context->default_realms) {
+    if (ret == 0 && context->default_realms) {
+        krb5_free_host_realm(context, p->default_realms);
        ret = krb5_copy_host_realm(context,
                                   context->default_realms, &p->default_realms);
-       if (ret)
-           goto out;
     }
 
-    ret = _krb5_config_copy(context, context->cf, &p->cf);
-    if (ret)
-       goto out;
-
     /* XXX should copy */
-    _krb5_init_ets(p);
-
-    cc_ops_copy(p, context);
-    kt_ops_copy(p, context);
-
-    ret = krb5_set_extra_addresses(p, context->extra_addresses);
-    if (ret)
-       goto out;
-    ret = krb5_set_extra_addresses(p, context->ignore_addresses);
-    if (ret)
-       goto out;
-
-    ret = _krb5_copy_send_to_kdc_func(p, context);
-    if (ret)
-       goto out;
-
-    *out = p;
-
-    return 0;
-
- out:
-    krb5_free_context(p);
+    if (ret == 0)
+        _krb5_init_ets(p);
+
+    if (ret == 0)
+        ret = cc_ops_copy(p, context);
+    if (ret == 0)
+        ret = kt_ops_copy(p, context);
+    if (ret == 0)
+        ret = krb5_set_extra_addresses(p, context->extra_addresses);
+    if (ret == 0)
+        ret = krb5_set_extra_addresses(p, context->ignore_addresses);
+    if (ret == 0)
+        ret = _krb5_copy_send_to_kdc_func(p, context);
+
+    if (ret == 0)
+        *out = p;
+    else
+        krb5_free_context(p);
     return ret;
 }
 
index fc371c637764cbfe6a6187868ef190b6c3adb1ae..56261b29fa68fc9023ca97e61028113eb1d71db5 100644 (file)
@@ -32,7 +32,6 @@
  */
 
 #include "krb5_locl.h"
-#include "krb5-v4compat.h"
 
 #ifndef HEIMDAL_SMALLER
 
@@ -58,7 +57,6 @@ krb524_convert_creds_kdc(krb5_context context,
                         struct credentials *v4creds)
     KRB5_DEPRECATED_FUNCTION("Use X instead")
 {
-    memset(v4creds, 0, sizeof(*v4creds));
     krb5_set_error_message(context, EINVAL,
                           N_("krb524_convert_creds_kdc not supported", ""));
     return EINVAL;
@@ -86,7 +84,6 @@ krb524_convert_creds_kdc_ccache(krb5_context context,
                                struct credentials *v4creds)
     KRB5_DEPRECATED_FUNCTION("Use X instead")
 {
-    memset(v4creds, 0, sizeof(*v4creds));
     krb5_set_error_message(context, EINVAL,
                           N_("krb524_convert_creds_kdc_ccache not supported", ""));
     return EINVAL;
index a75fc03f985d33a3e3d51e1992c7d08b323438b5..f20fc51936fab38aabab2e5d9e223063d90c6a28 100644 (file)
@@ -238,8 +238,7 @@ kcm_alloc(krb5_context context,
 
     if (residual) {
         /* KCM cache names must start with {UID} or {UID}: */
-        if (residual[0] != '0')
-            plen = strspn(residual, "0123456789");
+        plen = strspn(residual, "0123456789");
         if (plen && residual[plen] != ':' && residual[plen] != '\0')
             plen = 0;
         /*
index 559d640f002ca158cfb3b95b62618013bc220667..bcb3ed83733172713322ef7464c675c7e27c196f 100644 (file)
@@ -883,7 +883,8 @@ krb5_kt_add_entry(krb5_context context,
                               id->prefix);
        return KRB5_KT_NOWRITE;
     }
-    entry->timestamp = time(NULL);
+    if (entry->timestamp == 0)
+        entry->timestamp = time(NULL);
     return (*id->add)(context, id,entry);
 }
 
diff --git a/third_party/heimdal/lib/krb5/krb5-v4compat.h b/third_party/heimdal/lib/krb5/krb5-v4compat.h
deleted file mode 100644 (file)
index 2992976..0000000
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (c) 1997 - 2003 Kungliga Tekniska Högskolan
- * (Royal Institute of Technology, Stockholm, Sweden).
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * 3. Neither the name of the Institute nor the names of its contributors
- *    may be used to endorse or promote products derived from this software
- *    without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-/* $Id$ */
-
-#ifndef __KRB5_V4COMPAT_H__
-#define __KRB5_V4COMPAT_H__
-
-#include "krb_err.h"
-
-/*
- * This file must only be included with v4 compat glue stuff in
- * heimdal sources.
- *
- * It MUST NOT be installed.
- */
-
-#define                KRB_PROT_VERSION        4
-
-#define                AUTH_MSG_KDC_REQUEST                     (1<<1)
-#define        AUTH_MSG_KDC_REPLY                       (2<<1)
-#define                AUTH_MSG_APPL_REQUEST                    (3<<1)
-#define                AUTH_MSG_APPL_REQUEST_MUTUAL             (4<<1)
-#define                AUTH_MSG_ERR_REPLY                       (5<<1)
-#define                AUTH_MSG_PRIVATE                         (6<<1)
-#define                AUTH_MSG_SAFE                            (7<<1)
-#define                AUTH_MSG_APPL_ERR                        (8<<1)
-#define                AUTH_MSG_KDC_FORWARD                     (9<<1)
-#define                AUTH_MSG_KDC_RENEW                      (10<<1)
-#define        AUTH_MSG_DIE                            (63<<1)
-
-/* General definitions */
-#define                KSUCCESS        0
-#define                KFAILURE        255
-
-/* */
-
-#define                MAX_KTXT_LEN    1250
-
-#define        ANAME_SZ        40
-#define                REALM_SZ        40
-#define                SNAME_SZ        40
-#define                INST_SZ         40
-
-struct ktext {
-    unsigned int length;               /* Length of the text */
-    unsigned char dat[MAX_KTXT_LEN];   /* The data itself */
-    uint32_t mbz;              /* zero to catch runaway strings */
-};
-
-struct credentials {
-    char    service[ANAME_SZ]; /* Service name */
-    char    instance[INST_SZ]; /* Instance */
-    char    realm[REALM_SZ];   /* Auth domain */
-    char    session[8];                /* Session key */
-    int     lifetime;          /* Lifetime */
-    int     kvno;              /* Key version number */
-    struct ktext ticket_st;    /* The ticket itself */
-    int32_t    issue_date;     /* The issue time */
-    char    pname[ANAME_SZ];   /* Principal's name */
-    char    pinst[INST_SZ];    /* Principal's instance */
-};
-
-#define TKTLIFENUMFIXED 64
-#define TKTLIFEMINFIXED 0x80
-#define TKTLIFEMAXFIXED 0xBF
-#define TKTLIFENOEXPIRE 0xFF
-#define MAXTKTLIFETIME (30*24*3600)    /* 30 days */
-#ifndef NEVERDATE
-#define NEVERDATE ((time_t)0x7fffffffL)
-#endif
-
-#define                KERB_ERR_NULL_KEY       10
-
-#define        CLOCK_SKEW      5*60
-
-#ifndef TKT_ROOT
-#define TKT_ROOT "%{TEMP}/tkt"
-#endif
-
-struct _krb5_krb_auth_data {
-    int8_t  k_flags;           /* Flags from ticket */
-    char    *pname;            /* Principal's name */
-    char    *pinst;            /* His Instance */
-    char    *prealm;           /* His Realm */
-    uint32_t checksum;         /* Data checksum (opt) */
-    krb5_keyblock session;     /* Session Key */
-    unsigned char life;                /* Life of ticket */
-    uint32_t time_sec;         /* Time ticket issued */
-    uint32_t address;          /* Address in ticket */
-};
-
-KRB5_LIB_FUNCTION time_t KRB5_LIB_CALL
-_krb5_krb_life_to_time (int, int);
-
-KRB5_LIB_FUNCTION int KRB5_LIB_CALL
-_krb5_krb_time_to_life (time_t, time_t);
-
-KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
-_krb5_krb_tf_setup (krb5_context, struct credentials *,
-                   const char *, int);
-
-KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
-_krb5_krb_dest_tkt(krb5_context, const char *);
-
-#define krb_time_to_life       _krb5_krb_time_to_life
-#define krb_life_to_time       _krb5_krb_life_to_time
-
-#endif /*  __KRB5_V4COMPAT_H__ */
index c8f355c81790c08c8a7b75f96e20106c6d3244b6..b923981908d3d1d6b60218508514e469b6c785c8 100644 (file)
@@ -882,6 +882,7 @@ verify_logonname(krb5_context context,
     }
     ret = krb5_storage_read(sp, s, len);
     if (ret != len) {
+       free(s);
        krb5_storage_free(sp);
        krb5_set_error_message(context, EINVAL, "Failed to read PAC logon name");
        return EINVAL;
@@ -894,8 +895,10 @@ verify_logonname(krb5_context context,
        unsigned int flags = WIND_RW_LE;
 
        ucs2 = malloc(sizeof(ucs2[0]) * ucs2len);
-       if (ucs2 == NULL)
+       if (ucs2 == NULL) {
+           free(s);
            return krb5_enomem(context);
+       }
 
        ret = wind_ucs2read(s, len, &flags, ucs2, &ucs2len);
        free(s);
index c9a6e3e8f8b92d97476c587482c92ca2aa4d25b3..0501728d3e5a8d62e86091c5095883c52b3a7a9d 100644 (file)
@@ -109,7 +109,7 @@ integer_to_BN(krb5_context context, const char *field, const heim_integer *f)
 }
 
 static krb5_error_code
-select_dh_group(krb5_context context, DH *dh, unsigned long bits,
+select_dh_group(krb5_context context, DH *dh, unsigned long min_bits,
                struct krb5_dh_moduli **moduli)
 {
     const struct krb5_dh_moduli *m;
@@ -118,25 +118,25 @@ select_dh_group(krb5_context context, DH *dh, unsigned long bits,
         krb5_set_error_message(context, EINVAL,
                                N_("Did not find a DH group parameter "
                                   "matching requirement of %lu bits", ""),
-                               bits);
+                               min_bits);
         return EINVAL;
     }
 
-    if (bits == 0) {
+    if (min_bits == 0) {
        m = moduli[1]; /* XXX */
        if (m == NULL)
            m = moduli[0]; /* XXX */
     } else {
        int i;
        for (i = 0; moduli[i] != NULL; i++) {
-           if (bits < moduli[i]->bits)
+           if (moduli[i]->bits >= min_bits)
                break;
        }
        if (moduli[i] == NULL) {
            krb5_set_error_message(context, EINVAL,
                                   N_("Did not find a DH group parameter "
                                      "matching requirement of %lu bits", ""),
-                                  bits);
+                                  min_bits);
            return EINVAL;
        }
        m = moduli[i];
@@ -2164,7 +2164,7 @@ static const char *default_moduli_rfc3526_MODP_group14 =
     /* name */
     "rfc3526-MODP-group14 "
     /* bits */
-    "1760 "
+    "2048 "
     /* p */
     "FFFFFFFF" "FFFFFFFF" "C90FDAA2" "2168C234" "C4C6628B" "80DC1CD1"
     "29024E08" "8A67CC74" "020BBEA6" "3B139B22" "514A0879" "8E3404DD"
index 086f2edcd5db58a43e5e3ec2f6c878fc781645b8..a854e5c4ae931821f5073056f1e1f8cd40aede4c 100644 (file)
@@ -1192,7 +1192,7 @@ krb5_sendto_context(krb5_context context,
                break;
            }
            action = KRB5_SENDTO_KRBHST;
-            fallthrough;
+            HEIM_FALLTHROUGH;
        case KRB5_SENDTO_KRBHST:
            if (ctx->krbhst == NULL) {
                ret = krb5_krbhst_init_flags(context, realm, type,
@@ -1214,7 +1214,7 @@ krb5_sendto_context(krb5_context context,
                handle = heim_retain(ctx->krbhst);
            }
            action = KRB5_SENDTO_TIMEOUT;
-            fallthrough;
+            HEIM_FALLTHROUGH;
        case KRB5_SENDTO_TIMEOUT:
 
            /*
index 79f8e3adbab096d6b96d66d6836e371448ce582b..8b966f83e795245a5d5cf1aca8e38459eeb1cdbf 100644 (file)
@@ -1002,6 +1002,8 @@ krb5_ret_string(krb5_storage *sp,
 {
     int ret;
     krb5_data data;
+
+    *string = NULL;
     ret = krb5_ret_data(sp, &data);
     if(ret)
        return ret;
index 059e5abc6a4a418f23f8e1ee4e2c17e06d52ee2e..a9dbc4755987e07b1677be419b7668346f9f3a75 100644 (file)
@@ -48,8 +48,8 @@
 /* Define to 1 if you have the <limits.h> header file. */
 #undef HAVE_LIMITS_H
 
-/* Define to 1 if you have the <memory.h> header file. */
-#undef HAVE_MEMORY_H
+/* Define to 1 if you have the <minix/config.h> header file. */
+#undef HAVE_MINIX_CONFIG_H
 
 /* Define to 1 if you have the <ncurses.h> header file. */
 #undef HAVE_NCURSES_H
@@ -67,6 +67,9 @@
 /* Define to 1 if you have the <stdint.h> header file. */
 #undef HAVE_STDINT_H
 
+/* Define to 1 if you have the <stdio.h> header file. */
+#undef HAVE_STDIO_H
+
 /* Define to 1 if you have the <stdlib.h> header file. */
 #undef HAVE_STDLIB_H
 
 /* Define to 1 if you have the <vfork.h> header file. */
 #undef HAVE_VFORK_H
 
+/* Define to 1 if you have the <wchar.h> header file. */
+#undef HAVE_WCHAR_H
+
 /* Define to 1 if you have the `wcsdup' function. */
 #undef HAVE_WCSDUP
 
 /* Define as the return type of signal handlers (`int' or `void'). */
 #undef RETSIGTYPE
 
-/* Define to 1 if you have the ANSI C header files. */
+/* Define to 1 if all of the C90 standard headers exist (not just the ones
+   required in a freestanding environment). This macro is provided for
+   backward compatibility; new code need not use it. */
 #undef STDC_HEADERS
 
 /* Enable extensions on AIX 3, Interix.  */
 #ifndef _ALL_SOURCE
 # undef _ALL_SOURCE
 #endif
+/* Enable general extensions on macOS.  */
+#ifndef _DARWIN_C_SOURCE
+# undef _DARWIN_C_SOURCE
+#endif
+/* Enable general extensions on Solaris.  */
+#ifndef __EXTENSIONS__
+# undef __EXTENSIONS__
+#endif
 /* Enable GNU extensions on systems that have them.  */
 #ifndef _GNU_SOURCE
 # undef _GNU_SOURCE
 #endif
-/* Enable threading extensions on Solaris.  */
+/* Enable X/Open compliant socket functions that do not require linking
+   with -lxnet on HP-UX 11.11.  */
+#ifndef _HPUX_ALT_XOPEN_SOCKET_API
+# undef _HPUX_ALT_XOPEN_SOCKET_API
+#endif
+/* Identify the host operating system as Minix.
+   This macro does not affect the system headers' behavior.
+   A future release of Autoconf may stop defining this macro.  */
+#ifndef _MINIX
+# undef _MINIX
+#endif
+/* Enable general extensions on NetBSD.
+   Enable NetBSD compatibility extensions on Minix.  */
+#ifndef _NETBSD_SOURCE
+# undef _NETBSD_SOURCE
+#endif
+/* Enable OpenBSD compatibility extensions on NetBSD.
+   Oddly enough, this does nothing on OpenBSD.  */
+#ifndef _OPENBSD_SOURCE
+# undef _OPENBSD_SOURCE
+#endif
+/* Define to 1 if needed for POSIX-compatible behavior.  */
+#ifndef _POSIX_SOURCE
+# undef _POSIX_SOURCE
+#endif
+/* Define to 2 if needed for POSIX-compatible behavior.  */
+#ifndef _POSIX_1_SOURCE
+# undef _POSIX_1_SOURCE
+#endif
+/* Enable POSIX-compatible threading on Solaris.  */
 #ifndef _POSIX_PTHREAD_SEMANTICS
 # undef _POSIX_PTHREAD_SEMANTICS
 #endif
+/* Enable extensions specified by ISO/IEC TS 18661-5:2014.  */
+#ifndef __STDC_WANT_IEC_60559_ATTRIBS_EXT__
+# undef __STDC_WANT_IEC_60559_ATTRIBS_EXT__
+#endif
+/* Enable extensions specified by ISO/IEC TS 18661-1:2014.  */
+#ifndef __STDC_WANT_IEC_60559_BFP_EXT__
+# undef __STDC_WANT_IEC_60559_BFP_EXT__
+#endif
+/* Enable extensions specified by ISO/IEC TS 18661-2:2015.  */
+#ifndef __STDC_WANT_IEC_60559_DFP_EXT__
+# undef __STDC_WANT_IEC_60559_DFP_EXT__
+#endif
+/* Enable extensions specified by ISO/IEC TS 18661-4:2015.  */
+#ifndef __STDC_WANT_IEC_60559_FUNCS_EXT__
+# undef __STDC_WANT_IEC_60559_FUNCS_EXT__
+#endif
+/* Enable extensions specified by ISO/IEC TS 18661-3:2015.  */
+#ifndef __STDC_WANT_IEC_60559_TYPES_EXT__
+# undef __STDC_WANT_IEC_60559_TYPES_EXT__
+#endif
+/* Enable extensions specified by ISO/IEC TR 24731-2:2010.  */
+#ifndef __STDC_WANT_LIB_EXT2__
+# undef __STDC_WANT_LIB_EXT2__
+#endif
+/* Enable extensions specified by ISO/IEC 24747:2009.  */
+#ifndef __STDC_WANT_MATH_SPEC_FUNCS__
+# undef __STDC_WANT_MATH_SPEC_FUNCS__
+#endif
 /* Enable extensions on HP NonStop.  */
 #ifndef _TANDEM_SOURCE
 # undef _TANDEM_SOURCE
 #endif
-/* Enable general extensions on Solaris.  */
-#ifndef __EXTENSIONS__
-# undef __EXTENSIONS__
+/* Enable X/Open extensions.  Define to 500 only if necessary
+   to make mbstate_t available.  */
+#ifndef _XOPEN_SOURCE
+# undef _XOPEN_SOURCE
 #endif
 
 
 /* Version number of package */
 #undef VERSION
 
-/* Define to 1 if on MINIX. */
-#undef _MINIX
-
-/* Define to 2 if the system does not provide POSIX.1 features except with
-   this defined. */
-#undef _POSIX_1_SOURCE
-
-/* Define to 1 if you need to in order for `stat' and other things to work. */
-#undef _POSIX_SOURCE
-
 /* Define to empty if `const' does not conform to ANSI C. */
 #undef const
 
-/* Define to `int' if <sys/types.h> does not define. */
+/* Define as a signed integer type capable of holding a process identifier. */
 #undef pid_t
 
 /* Define to `unsigned int' if <sys/types.h> does not define. */
index 761e1f497fcb9c7ddbfc9d90c5b9f3867690db21..42c39ff23e15a994e32f9fb2eb67a737bde64f55 100644 (file)
@@ -471,7 +471,7 @@ heim_digest_generate_challenge(heim_digest_t context)
            break;
        case HEIM_DIGEST_TYPE_AUTO:
            context->type = HEIM_DIGEST_TYPE_RFC2831;
-            fallthrough;
+            HEIM_FALLTHROUGH;
        case HEIM_DIGEST_TYPE_RFC2831:
            asprintf(&challenge, "realm=\"%s\",nonce=\"%s\",qop=\"%s\",algorithm=md5-sess,charset=utf-8,maxbuf=%s",
                     context->serverRealm, context->serverNonce, context->serverQOP, context->serverMaxbuf);
index d75752ea00075e39a0d9be3bedb486004b3df74d..7384e18076f46de71325cc357df41596b9a94154 100644 (file)
@@ -682,7 +682,7 @@ heim_ntlm_decode_type1(const struct ntlm_buf *buf, struct ntlm_type1 *data)
     krb5_storage_set_byteorder(in, KRB5_STORAGE_BYTEORDER_LE);
 
     CHECK_SIZE(krb5_storage_read(in, sig, sizeof(sig)), sizeof(sig));
-    CHECK(memcmp(ntlmsigature, sig, sizeof(ntlmsigature)), 0);
+    CHECK(ct_memcmp(ntlmsigature, sig, sizeof(ntlmsigature)), 0);
     CHECK(krb5_ret_uint32(in, &type), 0);
     CHECK(type, 1);
     CHECK(krb5_ret_uint32(in, &data->flags), 0);
@@ -844,7 +844,7 @@ heim_ntlm_decode_type2(const struct ntlm_buf *buf, struct ntlm_type2 *type2)
     krb5_storage_set_byteorder(in, KRB5_STORAGE_BYTEORDER_LE);
 
     CHECK_SIZE(krb5_storage_read(in, sig, sizeof(sig)), sizeof(sig));
-    CHECK(memcmp(ntlmsigature, sig, sizeof(ntlmsigature)), 0);
+    CHECK(ct_memcmp(ntlmsigature, sig, sizeof(ntlmsigature)), 0);
     CHECK(krb5_ret_uint32(in, &type), 0);
     CHECK(type, 2);
 
@@ -1001,7 +1001,7 @@ heim_ntlm_decode_type3(const struct ntlm_buf *buf,
     krb5_storage_set_byteorder(in, KRB5_STORAGE_BYTEORDER_LE);
 
     CHECK_SIZE(krb5_storage_read(in, sig, sizeof(sig)), sizeof(sig));
-    CHECK(memcmp(ntlmsigature, sig, sizeof(ntlmsigature)), 0);
+    CHECK(ct_memcmp(ntlmsigature, sig, sizeof(ntlmsigature)), 0);
     CHECK(krb5_ret_uint32(in, &type), 0);
     CHECK(type, 3);
     CHECK(ret_sec_buffer(in, &lm), 0);
@@ -1825,7 +1825,7 @@ verify_ntlm2(const void *key, size_t len,
     if (ret)
         goto out;
 
-    if (memcmp(serveranswer, clientanswer, 16) != 0) {
+    if (ct_memcmp(serveranswer, clientanswer, 16) != 0) {
        heim_ntlm_free_buf(infotarget);
        return HNTLM_ERR_AUTH;
     }
index 8a1c743cc830b544ba675ef02f33daeb9f5060ae..373f9295a5e2107e5ee21b9aecb4bf89cd7e5641 100644 (file)
@@ -49,7 +49,7 @@ otp_verify_user_1 (OtpContext *ctx, const char *passwd)
   }
   memcpy (key2, key1, sizeof(key1));
   ctx->alg->next (key2);
-  if (memcmp (ctx->key, key2, sizeof(key2)) == 0) {
+  if (ct_memcmp (ctx->key, key2, sizeof(key2)) == 0) {
     --ctx->n;
     memcpy (ctx->key, key1, sizeof(key1));
     return 0;
index 35d753204e79dab90c02851106d88e1ce3166900..f6d038f3f6ea1c34650b8014c533e19f50dd7866 100644 (file)
@@ -201,7 +201,7 @@ libroken_la_LIBADD = @LTLIBOBJS@ $(LIB_crypt) $(LIB_pidfile)
 
 $(LTLIBOBJS) $(libroken_la_OBJECTS): roken.h $(XHEADERS)
 
-BUILT_SOURCES = roken.h $(err_h) $(fnmatch_h) $(ifadds_h) $(search_h) $(vis_h)
+BUILT_SOURCES = roken.h $(err_h) $(fnmatch_h) $(ifaddrs_h) $(search_h) $(vis_h)
 
 ## these are controlled by configure
 XHEADERS = $(err_h) $(fnmatch_h) $(ifaddrs_h) $(search_h) $(vis_h)
index ef74336d70a6618344ca3076380c9d354cd9bed9..4ec86ae90f19b01fb8defee30ab99cc0fb7d713c 100644 (file)
@@ -100,10 +100,10 @@ rk_base32_encode(const void *data, int size, char **str, enum rk_base32_flags fl
        p[6] = chars[(c & 0x0000000000000003e0ULL) >> 5];
        p[7] = chars[(c & 0x00000000000000001fULL) >> 0];
         switch (i - size) {
-        case 4: p[2] = p[3] = '=';  fallthrough;
-        case 3: p[4] = '=';         fallthrough;
-        case 2: p[5] = p[6] = '=';  fallthrough;
-        case 1: p[7] = '=';         fallthrough;
+        case 4: p[2] = p[3] = '=';  HEIM_FALLTHROUGH;
+        case 3: p[4] = '=';         HEIM_FALLTHROUGH;
+        case 2: p[5] = p[6] = '=';  HEIM_FALLTHROUGH;
+        case 1: p[7] = '=';         HEIM_FALLTHROUGH;
         default:                    break;
         }
        p += 8;
index dc4518ad5b0412704b6d1a3f9b80281528ad4fb1..2c6b5055be7557085bb336b391f4fe62f89adbeb 100644 (file)
@@ -148,7 +148,7 @@ int teardown_test(void)
 
         strcmp(dirname + len + 1 - sizeof(TESTDIR)/sizeof(char), TESTDIR) == 0) {
 
-        fallthrough;
+        HEIM_FALLTHROUGH;
 
     } else {
         /* did we create the directory? */
@@ -162,7 +162,7 @@ int teardown_test(void)
                     fprintf(stderr, "Can't change to test directory. Aborting cleanup.\n");
                     return -1;
                 } else {
-                    fallthrough;
+                    HEIM_FALLTHROUGH;
                 }
             } else {
                 return -1;
index 74f35283d3570da10606f9ecada4bf0167620e52..34da19f2cb1c0562425f19c59d144b7ed46925d1 100644 (file)
@@ -129,7 +129,7 @@ rk_fnmatch(const char *pattern, const char *string, int flags)
                                        --pattern;
                                }
                        }
-                        fallthrough;
+                        HEIM_FALLTHROUGH;
                default:
                        if (c != *string++)
                                return (FNM_NOMATCH);
index 12a26a712259639635e0724eb65f567c9c4d79e4..47affba59f5198e4265a8f229a79217ea778b8f0 100644 (file)
@@ -188,7 +188,7 @@ get_null (const struct addrinfo *hints,
     struct addrinfo *first = NULL;
     struct addrinfo **current = &first;
     int family = PF_UNSPEC;
-    int ret;
+    int ret = EAI_FAMILY;
 
     if (hints != NULL)
        family = hints->ai_family;
@@ -216,7 +216,7 @@ get_null (const struct addrinfo *hints,
                       &current, const_v4, &v4_addr, NULL);
     }
     *res = first;
-    return 0;
+    return ret;
 }
 
 static int
index 7fd2ca9f151fe02475097f1ee88080935adc6f79..09f4c73155a237a44face1699bc0075066f4ccac 100644 (file)
@@ -136,7 +136,7 @@ roken_get_homedir(char *home, size_t homesz)
         }
         return home;
     }
-    fallthrough;
+    HEIM_FALLTHROUGH;
 #else
 #ifdef HAVE_GETPWNAM_R
     size_t buflen = 2048;
index d76578abfe0ef040b962c0439216a1e3fb9da9ff..017eedf376bd836bb07dabed6e20aefddd2be475 100644 (file)
@@ -275,8 +275,7 @@ acc_flags(uint64_t res, int64_t val, uint64_t mult)
 }
 
 ROKEN_LIB_FUNCTION uint64_t ROKEN_LIB_CALL
-parse_flags (const char *s, const struct units *units,
-            int orig)
+parse_flags(const char *s, const struct units *units, uint64_t orig)
 {
     return parse_something_unsigned (s, units, NULL, acc_flags, orig, 1);
 }
index c5b6f2030a8880b8515243b41070896b9ea45de7..d17897dda60fe132f7730036cdde09f3fbd42661 100644 (file)
@@ -97,8 +97,8 @@ ROKEN_LIB_FUNCTION void ROKEN_LIB_CALL
 print_units_table (const struct units *units, FILE *f);
 
 ROKEN_LIB_FUNCTION uint64_t ROKEN_LIB_CALL
-parse_flags (const char *s, const struct units *units,
-            int orig);
+parse_flags(const char *s, const struct units *units,
+           uint64_t orig);
 
 ROKEN_LIB_FUNCTION int ROKEN_LIB_CALL
 unparse_units(int64_t num, const struct units *units, char *s, size_t len);
index 3da48962ee3ad1825da1b70a601f87243f264a8f..c8afedb26206adee4952b1d646fa9c9c99908b1a 100644 (file)
@@ -515,7 +515,7 @@ xyzprintf (struct snprintf_state *state, const char *char_format, va_list ap)
            }
            case '\0' :
                --format;
-                fallthrough;
+                HEIM_FALLTHROUGH;
            case '%' :
                (*state->append_char)(state, c);
                ++len;
index 9a951dd3008008a6e86fd9c84311ca513140681a..7be930755cad2c76b35808e7419afc2783a7cc2b 100644 (file)
@@ -377,7 +377,7 @@ strftime (char *buf, size_t maxsize, const char *format,
                break;
            case '\0' :
                --format;
-                fallthrough;
+                HEIM_FALLTHROUGH;
            case '%' :
                ret = snprintf (buf, maxsize - n,
                                "%%");
index 86216d2d6dcc6e4f54236600cb688f0f453566c5..b9345c083233edb4b8b2ce97560aab6473c91289 100644 (file)
@@ -424,7 +424,7 @@ strptime (const char *buf, const char *format, struct tm *timeptr)
                abort ();
            case '\0' :
                --format;
-                fallthrough;
+                HEIM_FALLTHROUGH;
            case '%' :
                if (*buf == '%')
                    ++buf;
index 38045c10048ac4c265fe0afce8dff3491c67ebf9..eed7fc10f0453587ea08f1aa9d3e3792d30411c4 100644 (file)
@@ -686,7 +686,7 @@ gen_wrapper(struct assignment *as)
        (*th->free)(s);
        free(s);
     }
-    cprint(1, "return 0;\n");
+    cprint(1, "return 1;\n");
     cprint(0, "}\n");
     cprint(0, "\n");
     free(n);
index 452b7b260d8445bd73c18134101f8ef51ca77e0c..4559109f3fd6aee1c65e8423795409ab15478d56 100644 (file)
@@ -205,18 +205,18 @@ wind_ucs4utf8(const uint32_t *in, size_t in_len, char *out, size_t *out_len)
            case 4:
                out[3] = (ch | 0x80) & 0xbf;
                ch = ch >> 6;
-                fallthrough;
+                HEIM_FALLTHROUGH;
            case 3:
                out[2] = (ch | 0x80) & 0xbf;
                ch = ch >> 6;
-                fallthrough;
+                HEIM_FALLTHROUGH;
            case 2:
                out[1] = (ch | 0x80) & 0xbf;
                ch = ch >> 6;
-                fallthrough;
+                HEIM_FALLTHROUGH;
            case 1:
                out[0] = ch | first_char[len - 1];
-                fallthrough;
+                HEIM_FALLTHROUGH;
             default:
                 break;
            }
@@ -486,14 +486,14 @@ wind_ucs2utf8(const uint16_t *in, size_t in_len, char *out, size_t *out_len)
            case 3:
                out[2] = (ch | 0x80) & 0xbf;
                ch = ch >> 6;
-                fallthrough;
+                HEIM_FALLTHROUGH;
            case 2:
                out[1] = (ch | 0x80) & 0xbf;
                ch = ch >> 6;
-                fallthrough;
+                HEIM_FALLTHROUGH;
            case 1:
                out[0] = ch | first_char[len - 1];
-                fallthrough;
+                HEIM_FALLTHROUGH;
             default:
                 break;
            }
index 934b0c17c7221f7900d3d98717eb8ed9f76d3e0b..4f2d355bf614bd169741cad2aba88e786e31876f 100644 (file)
@@ -76,7 +76,6 @@ INCFILES=\
        $(SDKINCDIR)\krb5\k5e1_err.h    \
        $(SDKINCDIR)\krb5\krb5-protos.h \
        $(SDKINCDIR)\krb5\krb5-types.h  \
-       $(SDKINCDIR)\krb5\krb5-v4compat.h       \
        $(SDKINCDIR)\krb5\krb5.h        \
        $(SDKINCDIR)\krb5\krb5_asn1.h   \
        $(SDKINCDIR)\krb5\krb5_ccapi.h  \
@@ -110,7 +109,6 @@ INCFILES=\
        $(SDKINCDIR)\heimdal\k5e1_err.h \
        $(SDKINCDIR)\heimdal\krb5-protos.h      \
        $(SDKINCDIR)\heimdal\krb5-types.h       \
-       $(SDKINCDIR)\heimdal\krb5-v4compat.h    \
        $(SDKINCDIR)\heimdal\krb5.h     \
        $(SDKINCDIR)\heimdal\krb5_asn1.h        \
        $(SDKINCDIR)\heimdal\krb5_ccapi.h       \
index aae031db645ebe06da1862f268de1d2e59d26682..01c4c2e7fa27d7ecc151fadc39a548e24c8dd4e1 100644 (file)
@@ -45,6 +45,7 @@ include @srcdirabs@/include-krb5.conf
         enable_virtual_hostbased_princs = true
         virtual_hostbased_princ_mindots = 1
         virtual_hostbased_princ_maxdots = 3
+        same_realm_aliases_are_soft = true
 
 [logging]
        kdc = 0-/FILE:@objdir@/messages.log
index b50239d844001509f11353d400864f0ca34cc07e..5109854fc26027cda1c07630773ec55fb19e3238 100644 (file)
@@ -42,18 +42,21 @@ testfailed="echo test failed; cat messages.log; exit 1"
 # If there is no useful db support compiled in, disable test
 ${have_db} || exit 77
 
+umask 077
+
 R=TEST.H5L.SE
 DCs="DC=test,DC=h5l,DC=se"
 
 port=@port@
 bx509port=@bx509port@
 
+server=datan.test.h5l.se
+otherserver=other.test.h5l.se
+
 kadmin="${kadmin} -l -r $R"
-bx509d="${bx509d} --reverse-proxied -p $bx509port"
+bx509d="${bx509d} --allow-GET --reverse-proxied -p $bx509port -H $server --cert=${objdir}/bx509.pem -t"
 kdc="${kdc} --addresses=localhost -P $port"
 
-server=datan.test.h5l.se
-otherserver=other.test.h5l.se
 cachefile="${objdir}/cache.krb5"
 cache="FILE:${cachefile}"
 cachefile2="${objdir}/cache2.krb5"
@@ -131,6 +134,55 @@ get_cert() {
          "$@" "$url"
 }
 
+get_with_token() {
+    if [ -n "$csr" ]; then
+        url="http://${server}:${bx509port}/${1}?csr=$csr${2}"
+    else
+        url="http://${server}:${bx509port}/${1}?${2}"
+    fi
+    shift 2
+
+    curl -fg --resolve ${server}:${bx509port}:127.0.0.1                 \
+         -H "Authorization: Negotiate $token"                           \
+         -D response-headers                                            \
+         "$@" "$url"                                                    &&
+        { echo "GET w/o CSRF token succeeded!"; exit 2; }
+    curl -g --resolve ${server}:${bx509port}:127.0.0.1                  \
+         -H "Authorization: Negotiate $token"                           \
+         -D response-headers                                            \
+         "$@" "$url"
+    grep ^X-CSRF-Token: response-headers >/dev/null ||
+        { echo "GET w/o CSRF token did not output a CSRF token!"; exit 2; }
+    curl -fg --resolve ${server}:${bx509port}:127.0.0.1                 \
+         -H "Authorization: Negotiate $token"                           \
+         -H "$(sed -e 's/\r//' response-headers | grep ^X-CSRF-Token:)" \
+         "$@" "$url"                                                    ||
+        { echo "GET w/ CSRF failed"; exit 2; }
+}
+
+get_via_POST() {
+    endpoint=$1
+    shift
+
+    curl -fg --resolve ${server}:${bx509port}:127.0.0.1                 \
+         -H "Authorization: Negotiate $token"                           \
+         -X POST -D response-headers                                    \
+         "$@" "http://${server}:${bx509port}/${endpoint}" &&
+        { echo "POST w/o CSRF token succeeded!"; exit 2; }
+    curl -g --resolve ${server}:${bx509port}:127.0.0.1                  \
+         -H "Authorization: Negotiate $token"                           \
+         -X POST -D response-headers                                    \
+         "$@" "http://${server}:${bx509port}/${endpoint}"
+    grep ^X-CSRF-Token: response-headers >/dev/null ||
+        { echo "POST w/o CSRF token did not output a CSRF token!"; exit 2; }
+    curl -fg --resolve ${server}:${bx509port}:127.0.0.1                 \
+         -H "Authorization: Negotiate $token"                           \
+         -H "$(sed -e 's/\r//' response-headers | grep ^X-CSRF-Token:)" \
+         -X POST                                                        \
+         "$@" "http://${server}:${bx509port}/${endpoint}" ||
+        { echo "POST w/ CSRF failed"; exit 2; }
+}
+
 rm -f $kt $ukt
 $ktutil -k $keytab add -r -V 1 -e aes128-cts-hmac-sha1-96               \
     -p HTTP/datan.test.h5l.se@${R} ||
@@ -292,15 +344,15 @@ ${kadmin} init \
     ${R} || exit 1
 ${kadmin} add -r --use-defaults foo@${R} || exit 1
 ${kadmin} add -r --use-defaults bar@${R} || exit 1
+${kadmin} add -r --use-defaults baz@${R} || exit 1
 ${kadmin} modify --pkinit-acl="CN=foo,DC=test,DC=h5l,DC=se" foo@${R} || exit 1
 
 
 echo "Starting bx509d"
-${bx509d} -H $server --cert=${objdir}/bx509.pem -t --daemon ||
-    { echo "bx509 failed to start"; exit 2; }
+${bx509d} --daemon || { echo "bx509 failed to start"; exit 2; }
 bx509pid=`getpid bx509d`
 
-trap "kill -9 ${bx509pid}; echo signal killing bx509d; exit 1;" EXIT
+trap 'kill -9 ${bx509pid}; echo signal killing bx509d; exit 1;' EXIT
 ec=0
 
 rm -f trivial.pem server.pem email.pem
@@ -310,12 +362,25 @@ csr_revoke
 $hxtool request-create  --subject='' --generate-key=rsa --key-bits=1024 \
                         --key=FILE:"${objdir}/k.der" "${objdir}/req" ||
     { echo "Failed to make a CSR"; exit 2; }
-csr=$($rkbase64 -- ${objdir}/req | $rkvis -h --stdin)
 
 # XXX Add autoconf check for curl?
 #     Create a barebones bx509 HTTP/1.1 client test program?
 
+echo "Fetching a trivial user certificate (no authentication, must fail)"
+# Encode the CSR in base64, then URL-encode it
+csr=$($rkbase64 -- ${objdir}/req | $rkvis -h --stdin)
+if (set -vx;
+    curl -g --resolve ${server}:${bx509port}:127.0.0.1                  \
+         -sf -o "${objdir}/trivial.pem"                                 \
+         "http://${server}:${bx509port}/bx509?csr=$csr"); then
+    $hxtool print --content "FILE:${objdir}/trivial.pem"
+    echo 'Got a certificate without authenticating!'
+    exit 1
+fi
+
 echo "Fetching a trivial user certificate"
+# Encode the CSR in base64, then URL-encode it
+csr=$($rkbase64 -- ${objdir}/req | $rkvis -h --stdin)
 token=$(KRB5CCNAME=$cache $gsstoken HTTP@$server)
 if (set -vx; get_cert '' -sf -o "${objdir}/trivial.pem"); then
     $hxtool print --content "FILE:${objdir}/trivial.pem"
@@ -336,6 +401,43 @@ else
     exit 1
 fi
 
+echo "Fetching a trivial user certificate (with POST, no auth, must fail)"
+# Encode the CSR in base64; curl will URL-encode it for us
+csr=$($rkbase64 -- ${objdir}/req)
+if (set -vx;
+    curl -fg --resolve ${server}:${bx509port}:127.0.0.1                 \
+         -X POST -D response-headers                                    \
+         -F csr="$csr" -o "${objdir}/trivial.pem"                       \
+         "http://${server}:${bx509port}/bx509" ); then
+    $hxtool print --content "FILE:${objdir}/trivial.pem"
+    echo 'Got a certificate without authenticating!'
+    exit 1
+fi
+
+echo "Fetching a trivial user certificate (with POST)"
+# Encode the CSR in base64; curl will URL-encode it for us
+csr=$($rkbase64 -- ${objdir}/req)
+token=$(KRB5CCNAME=$cache $gsstoken HTTP@$server)
+if (set -vx;
+    get_via_POST bx509 -F csr="$csr" -o "${objdir}/trivial.pem"); then
+    $hxtool print --content "FILE:${objdir}/trivial.pem"
+    if $hxtool acert --end-entity                                            \
+                    --expr="%{certificate.subject} == \"CN=foo,$DCs\""  \
+                    -P "foo@${R}" "FILE:${objdir}/trivial.pem"; then
+        echo 'Successfully obtained a trivial client certificate!'
+    else
+        echo 'FAIL: Obtained a trivial client certificate w/o expected PKINIT SAN)'
+        exit 1
+    fi
+    if $hxtool acert --expr="%{certificate.subject} == \"OU=Users,$DCs\""   \
+                     --has-private-key "FILE:${objdir}/trivial.pem"; then
+        echo 'Successfully obtained a trivial client certificate!'
+    fi
+else
+    echo 'Failed to get a certificate!'
+    exit 1
+fi
+
 echo "Checking that authorization is enforced"
 csr_revoke
 get_cert '&rfc822Name=foo@bar.example' -vvv -o "${objdir}/bad1.pem"
@@ -430,10 +532,10 @@ ${kadmin} ext_keytab -r -k $ukeytab foo@${R} || exit 1
 echo "Starting kdc";
 ${kdc} --detach --testing || { echo "kdc failed to start"; cat messages.log; exit 1; }
 kdcpid=`getpid kdc`
-trap "kill -9 ${kdcpid} ${bx509pid}; echo signal killing kdc and bx509d; exit 1;" EXIT
+trap 'kill -9 ${kdcpid} ${bx509pid}; echo signal killing kdc and bx509d; exit 1;' EXIT
 
 ${kinit} -kt $ukeytab foo@${R} || exit 1
-$klist || { echo "failed to setup kimpersonate credentials"; exit 2; }
+$klist || { echo "failed to kinit"; exit 2; }
 
 echo "Fetch TGT (not granted for other)"
 token=$(KRB5CCNAME=$cache $gsstoken HTTP@$server)
@@ -474,7 +576,7 @@ if ! (set -vx;
     exit 2
 fi
 ${kgetcred} -H HTTP/${server}@${R} ||
-    { echo "Trivial offline CA test failed (TGS)"; exit 2; }
+    { echo "Fetched TGT didn't work"; exit 2; }
 ${klist} | grep Addresses:.IPv4:8.8.8.8 ||
     { echo "Failed to get a TGT with /get-tgt end-point with addresses"; exit 2; }
 
@@ -491,7 +593,7 @@ if ! (set -vx;
     exit 2
 fi
 ${kgetcred} -H HTTP/${server}@${R} ||
-    { echo "Trivial offline CA test failed (TGS)"; exit 2; }
+    { echo "Fetched TGT didn't work"; exit 2; }
 ${klist} | grep Addresses:.IPv4:8.8.8.8 ||
     { echo "Failed to get a TGT with /get-tgt end-point with addresses"; exit 2; }
 
@@ -509,7 +611,7 @@ if ! (set -vx;
     exit 2
 fi
 ${kgetcred} -H HTTP/${server}@${R} ||
-    { echo "Trivial offline CA test failed (TGS)"; exit 2; }
+    { echo "Fetched TGT didn't work"; exit 2; }
 if which jq >/dev/null; then
     if ! ${klistjson} | jq -e '
             (reduce (.tickets[0]|(.Issued,.Expires)|
@@ -535,7 +637,7 @@ if ! (set -vx;
     exit 2
 fi
 ${kgetcred} -H HTTP/${server}@${R} ||
-    { echo "Trivial offline CA test failed (TGS)"; exit 2; }
+    { echo "Fetched TGT didn't work"; exit 2; }
 if which jq >/dev/null; then
     if ! ${klistjson} | jq -e '
             (reduce (.tickets[0]|(.Issued,.Expires)|
@@ -561,7 +663,7 @@ if ! (set -vx;
     exit 2
 fi
 ${kgetcred} -H HTTP/${server}@${R} ||
-    { echo "Trivial offline CA test failed (TGS)"; exit 2; }
+    { echo "Fetched TGT didn't work"; exit 2; }
 if which jq >/dev/null; then
     if ! ${klistjson} | jq -e '
             (reduce (.tickets[0]|(.Issued,.Expires)|
@@ -573,6 +675,153 @@ if which jq >/dev/null; then
     fi
 fi
 
+echo "Fetch TGTs (batch, authz fail)"
+${kadmin} modify --max-ticket-life=10d krbtgt/${R}@${R}
+(set -vx; csr_grant pkinit bar@${R} foo@${R})
+${kdestroy}
+token=$(KRB5CCNAME=$cache2 $gsstoken HTTP@$server)
+if (set -vx;
+    curl -o "${cachefile}.json" -Lgsf                                   \
+         --resolve ${server}:${bx509port}:127.0.0.1                     \
+         -H "Authorization: Negotiate $token"                           \
+         "http://${server}:${bx509port}/get-tgts?cname=bar@${R}&cname=baz@${R}"); then
+    echo "Got TGTs with /get-tgts end-point that should have been denied"
+    exit 2
+fi
+
+echo "Fetch TGTs (batch, authz pass)"
+${kadmin} modify --max-ticket-life=10d krbtgt/${R}@${R}
+(csr_grant pkinit bar@${R} foo@${R})
+(csr_grant pkinit baz@${R} foo@${R})
+${kdestroy}
+token=$(KRB5CCNAME=$cache2 $gsstoken HTTP@$server)
+if ! (set -vx;
+    curl -vvvo "${cachefile}.json" -Lgsf                                \
+         --resolve ${server}:${bx509port}:127.0.0.1                     \
+         -H "Authorization: Negotiate $token"                           \
+         "http://${server}:${bx509port}/get-tgts?cname=bar@${R}&cname=baz@${R}"); then
+    echo "Failed to get TGTs batch"
+    exit 2
+fi
+if which jq >/dev/null; then
+    jq -e . "${cachefile}.json" > /dev/null ||
+        { echo "/get-tgts produced non-JSON"; exit 2; }
+
+    # Check bar@$R's tickets:
+    jq -r 'select(.name|startswith("bar@")).ccache' "${cachefile}.json" |
+        $rkbase64 -d -- - > "${cachefile}"
+    ${kgetcred} -H HTTP/${server}@${R} ||
+        { echo "Fetched TGT didn't work"; exit 2; }
+    ${klistjson} | jq -e --arg p bar@$R '.principal == $p' > /dev/null ||
+        { echo "/get-tgts produced wrong TGTs"; exit 2; }
+
+    # Check baz@$R's tickets:
+    jq -r 'select(.name|startswith("baz@")).ccache' "${cachefile}.json" |
+        $rkbase64 -d -- - > "${cachefile}"
+    ${kgetcred} -H HTTP/${server}@${R} ||
+        { echo "Fetched TGT didn't work"; exit 2; }
+    ${klistjson} | jq -e --arg p baz@$R '.principal == $p' > /dev/null ||
+        { echo "/get-tgts produced wrong TGTs"; exit 2; }
+fi
+
+echo "Fetch TGTs (batch, authz pass, one non-existent principal)"
+${kadmin} modify --max-ticket-life=10d krbtgt/${R}@${R}
+(csr_grant pkinit bar@${R} foo@${R})
+(csr_grant pkinit baz@${R} foo@${R})
+(csr_grant pkinit not@${R} foo@${R})
+${kdestroy}
+token=$(KRB5CCNAME=$cache2 $gsstoken HTTP@$server)
+if ! (set -vx;
+    curl -vvvo "${cachefile}.json" -Lgsf                                \
+         --resolve ${server}:${bx509port}:127.0.0.1                     \
+         -H "Authorization: Negotiate $token"                           \
+         "http://${server}:${bx509port}/get-tgts?cname=not@${R}&cname=bar@${R}&cname=baz@${R}"); then
+    echo "Failed to get TGTs batch including non-existent principal"
+    exit 2
+fi
+if which jq >/dev/null; then
+    set -vx
+    jq -e . "${cachefile}.json" > /dev/null ||
+        { echo "/get-tgts produced non-JSON"; exit 2; }
+    jq -es '.[]|select(.name|startswith("not@"))|(.error_code//empty)' "${cachefile}.json" > /dev/null ||
+        { echo "No error was reported for not@${R}!"; exit 2; }
+
+    # Check bar@$R's tickets:
+    jq -r 'select(.name|startswith("bar@")).ccache' "${cachefile}.json" |
+        $rkbase64 -d -- - > "${cachefile}"
+    ${kgetcred} -H HTTP/${server}@${R} ||
+        { echo "Fetched TGT didn't work"; exit 2; }
+    ${klistjson} | jq -e --arg p bar@$R '.principal == $p' > /dev/null ||
+        { echo "/get-tgts produced wrong TGTs"; exit 2; }
+
+    # Check baz@$R's tickets:
+    jq -r 'select(.name|startswith("baz@")).ccache' "${cachefile}.json" |
+        $rkbase64 -d -- - > "${cachefile}"
+    ${kgetcred} -H HTTP/${server}@${R} ||
+        { echo "Fetched TGT didn't work"; exit 2; }
+    ${klistjson} | jq -e --arg p baz@$R '.principal == $p' > /dev/null ||
+        { echo "/get-tgts produced wrong TGTs"; exit 2; }
+fi
+
+echo "killing bx509d (${bx509pid})"
+sh ${leaks_kill} bx509d $bx509pid || ec=1
+
+echo "Starting bx509d (csrf-protection-type=GET-with-token, POST-with-header)"
+${bx509d} --csrf-protection-type=GET-with-token \
+          --csrf-protection-type=POST-with-header --daemon || {
+    echo "bx509 failed to start"
+    exit 2
+}
+bx509pid=`getpid bx509d`
+
+${kinit} -kt $ukeytab foo@${R} || exit 1
+$klist || { echo "failed to kinit"; exit 2; }
+
+echo "Fetching a trivial user certificate (GET with CSRF token)"
+csr=$($rkbase64 -- ${objdir}/req | $rkvis -h --stdin)
+token=$(KRB5CCNAME=$cache $gsstoken HTTP@$server)
+if (set -vx; get_with_token get-cert '' -o "${objdir}/trivial.pem"); then
+    $hxtool print --content "FILE:${objdir}/trivial.pem"
+    if $hxtool acert --end-entity                                            \
+                    --expr="%{certificate.subject} == \"CN=foo,$DCs\""  \
+                    -P "foo@${R}" "FILE:${objdir}/trivial.pem"; then
+        echo 'Successfully obtained a trivial client certificate!'
+    else
+        echo 'FAIL: Obtained a trivial client certificate w/o expected PKINIT SAN)'
+        exit 1
+    fi
+    if $hxtool acert --expr="%{certificate.subject} == \"OU=Users,$DCs\""   \
+                     --has-private-key "FILE:${objdir}/trivial.pem"; then
+        echo 'Successfully obtained a trivial client certificate!'
+    fi
+else
+    echo 'Failed to get a certificate!'
+    exit 1
+fi
+
+echo "Fetching a trivial user certificate (POST with X-CSRF header, no token)"
+# Encode the CSR in base64, then URL-encode it
+csr=$($rkbase64 -- ${objdir}/req | $rkvis -h --stdin)
+token=$(KRB5CCNAME=$cache $gsstoken HTTP@$server)
+if (set -vx; get_cert '' -H 'X-CSRF: junk' -X POST -sf -o "${objdir}/trivial.pem"); then
+    $hxtool print --content "FILE:${objdir}/trivial.pem"
+    if $hxtool acert --end-entity                                            \
+                    --expr="%{certificate.subject} == \"CN=foo,$DCs\""  \
+                    -P "foo@${R}" "FILE:${objdir}/trivial.pem"; then
+        echo 'Successfully obtained a trivial client certificate!'
+    else
+        echo 'FAIL: Obtained a trivial client certificate w/o expected PKINIT SAN)'
+        exit 1
+    fi
+    if $hxtool acert --expr="%{certificate.subject} == \"OU=Users,$DCs\""   \
+                     --has-private-key "FILE:${objdir}/trivial.pem"; then
+        echo 'Successfully obtained a trivial client certificate!'
+    fi
+else
+    echo 'Failed to get a certificate!'
+    exit 1
+fi
+
 echo "Fetch negotiate token (pre-test)"
 # Do what /bnegotiate does, roughly, prior to testing /bnegotiate
 $hxtool request-create  --subject='' --generate-key=rsa --key-bits=1024 \
@@ -596,11 +845,9 @@ grep 'REQ.*wrongaddr=true' ${objdir}/messages.log |
 
 echo "Fetching a Negotiate token"
 token=$(KRB5CCNAME=$cache $gsstoken HTTP@$server)
+csr=
 if (set -vx;
-    curl -o negotiate-token -Lgsf                                       \
-         --resolve ${server}:${bx509port}:127.0.0.1                     \
-         -H "Authorization: Negotiate $token"                           \
-         "http://${server}:${bx509port}/bnegotiate?target=HTTP%40${server}"); then
+    get_with_token get-negotiate-token "target=HTTP%40${server}" -o "${objdir}/negotiate-token"); then
     # bx509 sends us a token w/o a newline for now; we add one because
     # gss-token expects it.
     test -s negotiate-token && echo >> negotiate-token
index f57f2af85928f82a4ce2620670fbcccf7eadfedd..816f753d0792176f23646dfe323fdde3b9dd1a6c 100644 (file)
@@ -133,9 +133,11 @@ fi
 
 # HTTP curl-opts
 HTTP() {
-    curl -g --resolve ${server}:${restport2}:127.0.0.1                  \
-            --resolve ${server}:${restport}:127.0.0.1                   \
-         -u: --negotiate $verbose "$@"
+    curl -g --resolve ${server}:${restport2}:127.0.0.1  \
+            --resolve ${server}:${restport}:127.0.0.1   \
+         -u: --negotiate $verbose                       \
+         -D response-headers                            \
+         "$@"
 }
 
 # get_config QPARAMS curl-opts
@@ -145,6 +147,23 @@ get_config() {
     HTTP $verbose "$@" "$url"
 }
 
+check_age() {
+    set -- $(grep -i ^Cache-Control: response-headers)
+    if [ $# -eq 0 ]; then
+        return 1
+    fi
+    shift
+    for param in "$@"; do
+        case "$param" in
+        no-store) true;;
+        max-age=0) return 1;;
+        max-age=*) true;;
+        *) return 1;;
+        esac
+    done
+    return 0;
+}
+
 # get_keytab QPARAMS curl-opts
 get_keytab() {
     url="http://${server}:${restport}/get-keys?$1"
@@ -163,9 +182,9 @@ get_keytab_POST() {
 
     get_keytab "$q" -X POST --data-binary @/dev/null -f "$@" && 
         { echo "POST succeeded w/o CSRF token!"; return 1; }
-    get_keytab "$q" -X POST --data-binary @/dev/null -D response-headers "$@"
+    get_keytab "$q" -X POST --data-binary @/dev/null "$@"
     grep ^X-CSRF-Token: response-headers >/dev/null || return 1
-    get_keytab "$q" -X POST --data-binary @/dev/null -D response-headers \
+    get_keytab "$q" -X POST --data-binary @/dev/null \
         -H "$(sed -e 's/\r//' response-headers | grep ^X-CSRF-Token:)" "$@"
     grep '^HTTP/1.1 200' response-headers >/dev/null || return $?
     return 0
@@ -174,7 +193,7 @@ get_keytab_POST() {
 get_keytab_POST_redir() {
     url="http://${server}:${restport}/get-keys?$1"
     shift
-    HTTP -X POST --data-binary @/dev/null -D response-headers "$@" "$url"
+    HTTP -X POST --data-binary @/dev/null "$@" "$url"
     grep ^X-CSRF-Token: response-headers >/dev/null ||
         { echo "POST w/o CSRF token had response w/o CSRF token!"; return 1; }
     HTTP -X POST --data-binary @/dev/null  -f                           \
@@ -218,6 +237,9 @@ ${kadmin} add -r --use-defaults HTTP/xyz.${domain}@${R} || exit 1
 ${kadmin} add_ns --key-rotation-epoch=-1d --key-rotation-period=5m  \
                  --max-ticket-life=1d --max-renewable-life=5d       \
                  --attributes= HTTP/ns.${domain}@${R} || exit 1
+${kadmin} add_ns --key-rotation-epoch=-1d --key-rotation-period=5m  \
+                 --max-ticket-life=1d --max-renewable-life=5d       \
+                 --attributes=ok-as-delegate host/.ns2.${domain}@${R} || exit 1
 ${kadmin} add -r --use-defaults HTTP/${server}@${R} || exit 1
 ${kadmin} ext_keytab -r -k $keytab  kadmin/admin@${R} || exit 1
 ${kadmin} ext_keytab -r -k $keytab  httpkadmind/admin@${R} || exit 1
@@ -292,6 +314,8 @@ ${ktutil} -k "${objdir}/extracted_keytab" list --keys > extracted_keytab.kadmin
     { echo "Failed to list keytab for $p"; exit 1; }
 get_keytab "dNSName=${hn}" -sf -o "${objdir}/extracted_keytab" ||
     { echo "Failed to get a keytab for $p with curl"; exit 1; }
+check_age
+grep -i ^Cache-Control response-headers
 ${ktutil} -k "${objdir}/extracted_keytab" list --keys > extracted_keytab.rest ||
     { echo "Failed to list keytab for $p"; exit 1; }
 cmp extracted_keytab.kadmin extracted_keytab.rest ||
@@ -416,7 +440,6 @@ ${ktutil} -k "${objdir}/extracted_keytab" list --keys > extracted_keytab.kadmin
 cmp extracted_keytab.kadmin extracted_keytab.rest ||
     { echo "Keytabs for $p don't match!"; exit 1; }
 
-if false; then
 hn=bar.ns.${domain}
 p=HTTP/$hn
 echo "Checking materialization of virtual principal ($p)"
@@ -435,7 +458,6 @@ ${ktutil} -k "${objdir}/extracted_keytab" list --keys > extracted_keytab.kadmin
     { echo "Failed to list keytab for $p"; exit 1; }
 cmp extracted_keytab.kadmin extracted_keytab.rest ||
     { echo "Keytabs for $p don't match!"; exit 1; }
-fi
 
 echo "Starting secondary httpkadmind to test HTTP redirection"
 ${httpkadmind2} --primary-server-uri=http://localhost:$restport         \
@@ -622,22 +644,21 @@ cmp extracted_keytab.rest1 extracted_keytab.rest2 > /dev/null &&
 test "$(grep $p extracted_keytab.rest2 | wc -l)" -eq 3 ||
     { echo "Wrong number of new keys!"; exit 1; }
 
-echo "Checking that host services as clients can self-serve"
+echo "Checking that host services as clients can self-create"
 hn=synthesized.${domain}
 p=host/$hn
-KRB5CCNAME=$admincache ${kadmin} get -s $p &&
+KRB5CCNAME=$admincache ${kadmin} get -s $p >/dev/null &&
     { echo "Internal error -- $p exists too soon"; exit 1; }
 ${kinit2} -C "FILE:${objdir}/pkinit-synthetic.crt,${keyfile2}" ${p}@${R} || \
     { echo "Failed to kinit with PKINIT client cert"; exit 1; }
 ${kgetcred2} HTTP/localhost@$R || echo WAT
-${klist2}
 rm -f extracted_keytab*
 KRB5CCNAME=$cache2 \
 get_keytab_POST "spn=$p&create=true" -s -o "${objdir}/extracted_keytab" ||
     { echo "Failed to create and extract host keys for self"; exit 1; }
-${ktutil} -k "${objdir}/extracted_keytab" list ||
+${ktutil} -k "${objdir}/extracted_keytab" list > /dev/null ||
     { echo "Failed to create and extract host keys for self (bogus keytab)"; exit 1; }
-KRB5CCNAME=$admincache ${kadmin} get -s $p ||
+KRB5CCNAME=$admincache ${kadmin} get -s $p >/dev/null ||
     { echo "Failed to create and extract host keys for self"; exit 1; }
 
 echo "Checking that host services can't get other host service principals"
@@ -646,8 +667,8 @@ p=host/$hn
 KRB5CCNAME=$cache2 \
 get_keytab_POST "spn=$p&create=true" -s -o "${objdir}/extracted_keytab2" &&
     { echo "Failed to fail to create and extract host keys for other!"; exit 1; }
-${ktutil} -k "${objdir}/extracted_keytab2" list || true
-KRB5CCNAME=$admincache ${kadmin} get -s $p &&
+${ktutil} -k "${objdir}/extracted_keytab2" list > /dev/null || true
+KRB5CCNAME=$admincache ${kadmin} get -s $p >/dev/null &&
     { echo "Failed to fail to create and extract host keys for other!"; exit 1; }
 
 echo "Checking that host services can't get keys for themselves and others"
@@ -657,15 +678,135 @@ p2=host/nonexistent.${domain}
 ${kinit2} -C "FILE:${objdir}/pkinit-synthetic.crt,${keyfile2}" ${p}@${R} || \
     { echo "Failed to kinit with PKINIT client cert"; exit 1; }
 ${kgetcred2} HTTP/localhost@$R || echo WAT
-${klist2}
 rm -f extracted_keytab*
 KRB5CCNAME=$cache2 \
 get_keytab_POST "spn=$p&spn=$p2&create=true" -s -o "${objdir}/extracted_keytab" &&
     { echo "Failed to fail to create and extract host keys for other!"; exit 1; }
-${ktutil} -k "${objdir}/extracted_keytab2" list || true
-KRB5CCNAME=$admincache ${kadmin} get -s $p2 &&
+${ktutil} -k "${objdir}/extracted_keytab2" list > /dev/null || true
+KRB5CCNAME=$admincache ${kadmin} get -s $p2 >/dev/null &&
     { echo "Failed to fail to create and extract host keys for other!"; exit 1; }
 
+echo "Checking that attributes for new principals can be configured"
+hn=a-particular-hostname.test.h5l.se
+p=host/$hn
+${hxtool} issue-certificate \
+          --ca-certificate=FILE:$objdir/ca.crt,${keyfile} \
+          --type="pkinit-client" \
+          --pk-init-principal="$p@$R" \
+          --req="PKCS10:req-pkinit.der" \
+          --lifetime=7d \
+          --certificate="FILE:pkinit-synthetic.crt" ||
+         { echo "Failed to make PKINIT client cert"; exit 1; }
+KRB5CCNAME=$admincache ${kadmin} get -s $p >/dev/null 2>&1 &&
+    { echo "Internal error -- $p exists too soon"; exit 1; }
+${kinit2} -C "FILE:${objdir}/pkinit-synthetic.crt,${keyfile2}" ${p}@${R} || \
+    { echo "Failed to kinit with PKINIT client cert"; exit 1; }
+${kgetcred2} HTTP/localhost@$R || echo WAT
+rm -f extracted_keytab*
+KRB5CCNAME=$cache2 \
+get_keytab_POST "spn=$p&create=true" -s -o "${objdir}/extracted_keytab" ||
+    { echo "Failed to create and extract host keys for self"; exit 1; }
+${ktutil} -k "${objdir}/extracted_keytab" list > /dev/null ||
+    { echo "Failed to create and extract host keys for self (bogus keytab)"; exit 1; }
+KRB5CCNAME=$admincache ${kadmin} get -s $p >/dev/null ||
+    { echo "Failed to create and extract host keys for self"; exit 1; }
+KRB5CCNAME=$admincache ${kadmin} get $p |
+    grep 'Attributes:.*ok-as-delegate' > /dev/null ||
+    { echo "Failed to create with configured attributes"; exit 1; }
+KRB5CCNAME=$admincache ${kadmin} get $p |
+    grep 'Attributes:.*no-auth-data-reqd' > /dev/null ||
+    { echo "Failed to create with configured attributes"; exit 1; }
+
+hn=other-hostname.test.h5l.se
+p=host/$hn
+${hxtool} issue-certificate \
+          --ca-certificate=FILE:$objdir/ca.crt,${keyfile} \
+          --type="pkinit-client" \
+          --pk-init-principal="$p@$R" \
+          --req="PKCS10:req-pkinit.der" \
+          --lifetime=7d \
+          --certificate="FILE:pkinit-synthetic.crt" ||
+         { echo "Failed to make PKINIT client cert"; exit 1; }
+KRB5CCNAME=$admincache ${kadmin} get -s $p >/dev/null 2>&1 &&
+    { echo "Internal error -- $p exists too soon"; exit 1; }
+${kinit2} -C "FILE:${objdir}/pkinit-synthetic.crt,${keyfile2}" ${p}@${R} || \
+    { echo "Failed to kinit with PKINIT client cert"; exit 1; }
+${kgetcred2} HTTP/localhost@$R || echo WAT
+rm -f extracted_keytab*
+KRB5CCNAME=$cache2 \
+get_keytab_POST "spn=$p&create=true" -s -o "${objdir}/extracted_keytab" ||
+    { echo "Failed to create and extract host keys for self"; exit 1; }
+${ktutil} -k "${objdir}/extracted_keytab" list > /dev/null ||
+    { echo "Failed to create and extract host keys for self (bogus keytab)"; exit 1; }
+KRB5CCNAME=$admincache ${kadmin} get -s $p >/dev/null ||
+    { echo "Failed to create and extract host keys for self"; exit 1; }
+KRB5CCNAME=$admincache ${kadmin} get $p |
+    grep 'Attributes:.*ok-as-delegate' > /dev/null &&
+    { echo "Create with unexpected attributes"; exit 1; }
+KRB5CCNAME=$admincache ${kadmin} get $p |
+    grep 'Attributes:.*no-auth-data-reqd' > /dev/null &&
+    { echo "Create with unexpected attributes"; exit 1; }
+
+hn=a-server.prod.test.h5l.se
+p=host/$hn
+${hxtool} issue-certificate \
+          --ca-certificate=FILE:$objdir/ca.crt,${keyfile} \
+          --type="pkinit-client" \
+          --pk-init-principal="$p@$R" \
+          --req="PKCS10:req-pkinit.der" \
+          --lifetime=7d \
+          --certificate="FILE:pkinit-synthetic.crt" ||
+         { echo "Failed to make PKINIT client cert"; exit 1; }
+KRB5CCNAME=$admincache ${kadmin} get -s $p >/dev/null 2>&1 &&
+    { echo "Internal error -- $p exists too soon"; exit 1; }
+${kinit2} -C "FILE:${objdir}/pkinit-synthetic.crt,${keyfile2}" ${p}@${R} || \
+    { echo "Failed to kinit with PKINIT client cert"; exit 1; }
+${kgetcred2} HTTP/localhost@$R || echo WAT
+rm -f extracted_keytab*
+KRB5CCNAME=$cache2 \
+get_keytab_POST "spn=$p&create=true" -s -o "${objdir}/extracted_keytab" ||
+    { echo "Failed to create and extract host keys for self"; exit 1; }
+${ktutil} -k "${objdir}/extracted_keytab" list > /dev/null ||
+    { echo "Failed to create and extract host keys for self (bogus keytab)"; exit 1; }
+KRB5CCNAME=$admincache ${kadmin} get -s $p >/dev/null ||
+    { echo "Failed to create and extract host keys for self"; exit 1; }
+KRB5CCNAME=$admincache ${kadmin} get $p |
+    grep 'Attributes:.*ok-as-delegate' > /dev/null ||
+    { echo "Failed to create with configured attributes"; exit 1; }
+KRB5CCNAME=$admincache ${kadmin} get $p |
+    grep 'Attributes:.*no-auth-data-reqd' > /dev/null ||
+    { echo "Failed to create with configured attributes"; exit 1; }
+
+hn=a-host.ns2.test.h5l.se
+p=host/$hn
+${hxtool} issue-certificate \
+          --ca-certificate=FILE:$objdir/ca.crt,${keyfile} \
+          --type="pkinit-client" \
+          --pk-init-principal="$p@$R" \
+          --req="PKCS10:req-pkinit.der" \
+          --lifetime=7d \
+          --certificate="FILE:pkinit-synthetic.crt" ||
+         { echo "Failed to make PKINIT client cert"; exit 1; }
+KRB5CCNAME=$admincache ${kadmin} get -s $p >/dev/null 2>&1 &&
+    { echo "Internal error -- $p exists too soon"; exit 1; }
+${kinit2} -C "FILE:${objdir}/pkinit-synthetic.crt,${keyfile2}" ${p}@${R} || \
+    { echo "Failed to kinit with PKINIT client cert"; exit 1; }
+${kgetcred2} HTTP/localhost@$R || echo WAT
+rm -f extracted_keytab*
+KRB5CCNAME=$cache2 \
+get_keytab_POST "spn=$p&create=true" -s -o "${objdir}/extracted_keytab" ||
+    { echo "Failed to create and extract host keys for self"; exit 1; }
+${ktutil} -k "${objdir}/extracted_keytab" list > /dev/null ||
+    { echo "Failed to create and extract host keys for self (bogus keytab)"; exit 1; }
+KRB5CCNAME=$admincache ${kadmin} get -s $p >/dev/null ||
+    { echo "Failed to create and extract host keys for self"; exit 1; }
+KRB5CCNAME=$admincache ${kadmin} get $p |
+    grep 'Attributes:.*ok-as-delegate' > /dev/null ||
+    { echo "Failed to create with namespace attributes"; exit 1; }
+KRB5CCNAME=$admincache ${kadmin} get $p |
+    grep 'Attributes:.*no-auth-data-reqd' > /dev/null &&
+    { echo "Create with unexpected attributes"; exit 1; }
+
 grep 'Internal error' messages.log &&
     { echo "Internal errors in log"; exit 1; }
 
index 45d679ceb4a4c82186045c8c90cad579cd9a733c..339868bfb8f3f9edada5a40780b3f675a718a53b 100644 (file)
@@ -100,6 +100,11 @@ echo Starting kdc ; > messages.log
 ${kdc} --detach --testing || { echo "kdc failed to start"; cat messages.log; exit 1; }
 kdcpid=`getpid kdc`
 
+echo Starting kadmind
+${kadmind} --detach --list-chunk-size=1 \
+    || { echo "kadmind failed to start"; cat messages.log; exit 1; }
+kadmpid=`getpid kadmind`
+
 trap "kill -9 ${kdcpid} ${kadmpid}" EXIT
 
 #----------------------------------
@@ -107,58 +112,34 @@ echo "kinit (no admin); test mod --alias authorization"
 ${kinit} --password-file=${objdir}/foopassword \
     -S kadmin/admin@${R} hasalias@${R} || exit 1
 
-${kadmind} -d &
-kadmpid=$!
-sleep 1
-
 # Check that one non-permitted alias -> failure
 env KRB5CCNAME=${cache} \
 ${kadmin} -p hasalias@${R} modify --alias=goodalias1@${R} --alias=badalias@${R} hasalias@${R} &&
        { echo "kadmin failed $?"; cat messages.log ; exit 1; }
-wait $kadmpid || { echo "kadmind failed $?"; cat messages.log ; exit 1; }
-
-${kadmind} -d &
-kadmpid=$!
-sleep 1
 
 # Check that all permitted aliases -> success
 env KRB5CCNAME=${cache} \
 ${kadmin} -p hasalias@${R} modify --alias=goodalias1@${R} --alias=goodalias2@${R} hasalias@${R} ||
        { echo "kadmin failed $?"; cat messages.log ; exit 1; }
-wait $kadmpid || { echo "kadmind failed $?"; cat messages.log ; exit 1; }
-
-${kadmind} -d &
-kadmpid=$!
-sleep 1
 
 # Check that we can drop aliases
 env KRB5CCNAME=${cache} \
 ${kadmin} -p hasalias@${R} modify --alias=goodalias3@${R} hasalias@${R} ||
        { echo "kadmin failed $?"; cat messages.log ; exit 1; }
-wait $kadmpid || { echo "kadmind failed $?"; cat messages.log ; exit 1; }
 ${kadmin} -l get hasalias@${R} | grep Aliases: > kadmin.tmp
 read junk aliases < kadmin.tmp
 rm kadmin.tmp
 [ "$aliases" != "goodalias3@${R}" ] && { echo "kadmind failed $?"; cat messages.log ; exit 1; }
 
-${kadmind} -d &
-kadmpid=$!
-sleep 1
-
 env KRB5CCNAME=${cache} \
 ${kadmin} -p hasalias@${R} modify --alias=goodalias1@${R} --alias=goodalias2@${R} --alias=goodalias3@${R} hasalias@${R} ||
        { echo "kadmin failed $?"; cat messages.log ; exit 1; }
-wait $kadmpid || { echo "kadmind failed $?"; cat messages.log ; exit 1; }
 ${kadmin} -l get hasalias@${R} | grep Aliases: > kadmin.tmp
 read junk aliases < kadmin.tmp
 rm kadmin.tmp
 [ "$aliases" != "goodalias1@${R} goodalias2@${R} goodalias3@${R}" ] && { echo "FOO failed $?"; cat messages.log ; exit 1; }
 
 #----------------------------------
-${kadmind} -d &
-kadmpid=$!
-sleep 1
-
 echo "kinit (no admin)"
 ${kinit} --password-file=${objdir}/foopassword \
     -S kadmin/admin@${R} bar@${R} || exit 1
@@ -171,10 +152,6 @@ ${kadmin} -l get kaka2@${R} > /dev/null ||
        { echo "kadmin failed $?"; cat messages.log ; exit 1; }
 
 #----------------------------------
-${kadmind} -d &
-kadmpid=$!
-sleep 1
-
 echo "kinit (no admin)"
 ${kinit} --password-file=${objdir}/foopassword \
     -S kadmin/admin@${R} baz@${R} || exit 1
@@ -184,10 +161,6 @@ ${kadmin} -p baz@${R} get bar@${R} > /dev/null ||
        { echo "kadmin failed $?"; cat messages.log ; exit 1; }
 
 #----------------------------------
-${kadmind} -d &
-kadmpid=$!
-sleep 1
-
 echo "kinit (no admin)"
 ${kinit} --password-file=${objdir}/foopassword \
     -S kadmin/admin@${R} baz@${R} || exit 1
@@ -197,10 +170,6 @@ ${kadmin} -p baz@${R} passwd -p "$foopassword" bar@${R} > /dev/null 2>/dev/null
        { echo "kadmin succesded $?"; cat messages.log ; exit 1; }
 
 #----------------------------------
-${kadmind} -d &
-kadmpid=$!
-sleep 1
-
 echo "kinit (no admin)"
 ${kinit} --password-file=${objdir}/foopassword \
     -S kadmin/admin@${R} baz@${R} || exit 1
@@ -210,10 +179,6 @@ ${kadmin} -p baz@${R} get bar@${R} > /dev/null ||
        { echo "kadmin failed $?"; cat messages.log ; exit 1; }
 
 #----------------------------------
-${kadmind} -d &
-kadmpid=$!
-sleep 1
-
 echo "kinit (no admin)"
 ${kinit} --password-file=${objdir}/foopassword \
     -S kadmin/admin@${R} bez@${R} || exit 1
@@ -223,10 +188,6 @@ ${kadmin} -p bez@${R} passwd -p "$foopassword" bar@${R} > /dev/null 2>/dev/null
        { echo "kadmin succesded $?"; cat messages.log ; exit 1; }
 
 #----------------------------------
-${kadmind} -d &
-kadmpid=$!
-sleep 1
-
 echo "kinit (no admin)"
 ${kinit} --password-file=${objdir}/foopassword \
     -S kadmin/admin@${R} fez@${R} || exit 1
@@ -236,10 +197,6 @@ ${kadmin} -p fez@${R} get bar@${R} > /dev/null ||
        { echo "kadmin failed $?"; cat messages.log ; exit 1; }
 
 #----------------------------------
-${kadmind} -d &
-kadmpid=$!
-sleep 1
-
 echo "kinit (no admin)"
 ${kinit} --password-file=${objdir}/foopassword \
     -S kadmin/admin@${R} fez@${R} || exit 1
@@ -249,10 +206,6 @@ ${kadmin} -p fez@${R} passwd -p "$foopassword" bar@${R} > /dev/null 2>/dev/null
        { echo "kadmin succesded $?"; cat messages.log ; exit 1; }
 
 #----------------------------------
-${kadmind} -d &
-kadmpid=$!
-sleep 1
-
 echo "kinit (admin)"
 ${kinit} --password-file=${objdir}/foopassword \
     -S kadmin/admin@${R} foo/admin@${R} || exit 1
@@ -268,10 +221,6 @@ ${kadmin} -p foo/admin@${R} add -p abc --use-defaults kaka@${R} &&
        { echo "kadmin succeeded $?"; cat messages.log ; exit 1; }
 
 #----------------------------------
-${kadmind} -d &
-kadmpid=$!
-sleep 1
-
 echo "kadmin get doesnotexists"
 env KRB5CCNAME=${cache} \
 ${kadmin} -p foo/admin@${R} get -s doesnotexists@${R} \
@@ -286,14 +235,10 @@ mv kadmin2.tmp kadmin.tmp
 grep -v ': connect' kadmin.tmp > kadmin2.tmp
 mv kadmin2.tmp kadmin.tmp
 
-cmp kadmin.tmp ${srcdir}/donotexists.txt || \
+diff kadmin.tmp ${srcdir}/donotexists.txt || \
     { echo "wrong response"; exit 1;}
 
 #----------------------------------
-${kadmind} -d &
-kadmpid=$!
-sleep 1
-
 echo "kadmin get pkinit-acl"
 env KRB5CCNAME=${cache} \
 ${kadmin} -p foo/admin@${R} get -o pkinit-acl pkinit@${R} \
@@ -301,10 +246,6 @@ ${kadmin} -p foo/admin@${R} get -o pkinit-acl pkinit@${R} \
        { echo "kadmin failed $?"; cat messages.log ; exit 1; }
 
 #----------------------------------
-${kadmind} -d &
-kadmpid=$!
-sleep 1
-
 echo "kadmin get -o principal"
 env KRB5CCNAME=${cache} \
 ${kadmin} -p foo/admin@${R} get -o principal bar@${R} \
@@ -316,10 +257,6 @@ fi
 
 
 #----------------------------------
-${kadmind} -d &
-kadmpid=$!
-sleep 1
-
 echo "kadmin get -o kvno"
 env KRB5CCNAME=${cache} \
 ${kadmin} -p foo/admin@${R} get -o kvno bar@${R} \
@@ -331,10 +268,6 @@ fi
 
 
 #----------------------------------
-${kadmind} -d &
-kadmpid=$!
-sleep 1
-
 echo "kadmin get -o princ_expire_time"
 env KRB5CCNAME=${cache} \
 ${kadmin} -p foo/admin@${R} get -o princ_expire_time bar@${R} \
@@ -345,67 +278,178 @@ if test "`cat kadmin.tmp`" != "Principal expires: never" ; then
 fi
 
 #----------------------------------
-${kadmind} -d &
-kadmpid=$!
-sleep 1
-
 echo "kadmin get -s -o attributes"
 env KRB5CCNAME=${cache} \
 ${kadmin} -p foo/admin@${R} get -s -o attributes bar@${R} \
-        > kadmin.tmp 2>&1 || \
+        > kadmin.tmp || \
        { echo "kadmin failed $?"; cat messages.log ; exit 1; }
 if test "`cat kadmin.tmp`" != "Attributes" ; then
    cat kadmin.tmp ; cat messages.log ; exit 1 ;
 fi
 
 #----------------------------------
-${kadmind} -d &
-kadmpid=$!
-sleep 1
-
 echo "kadmin prune"
 env KRB5CCNAME=${cache} \
 ${kadmin} prune --kvno=2 prune@${R} \
         > kadmin.tmp 2>&1 || \
         { echo "kadmin failed $?"; cat messages.log ; exit 1; }
-wait $kadmpid || { echo "kadmind failed $?"; cat messages.log ; exit 1; }
-
-${kadmind} -d &
-kadmpid=$!
-sleep 1
-
 env KRB5CCNAME=${cache} \
 ${kadmin} get prune@${R} \
         > kadmin.tmp 2>&1 || \
         { echo "kadmin failed $?"; cat messages.log ; exit 1; }
-wait $kadmpid || { echo "kadmind failed $?"; cat messages.log ; exit 1; }
-
 cat kadmin.tmp | ${EGREP} Keytypes: | cut -d: -f2 | tr ' ' '
 ' | sed 's/^.*[[]\(.*\)[]].*$/\1/' | grep '[0-9]' | sort -nu | tr -d '
 ' | ${EGREP} '^13$' > /dev/null || \
         { echo "kadmin prune failed $?"; cat messages.log ; exit 1; }
 
 #----------------------------------
-${kadmind} -d &
-kadmpid=$!
-sleep 1
-
 echo "kadmin pruneall"
 env KRB5CCNAME=${cache} \
 ${kadmin} get pruneall@${R} \
         > kadmin.tmp 2>&1 || \
         { echo "kadmin failed $?"; cat messages.log ; exit 1; }
-wait $kadmpid || { echo "kadmind failed $?"; cat messages.log ; exit 1; }
-
 cat kadmin.tmp | ${EGREP} Keytypes: | cut -d: -f2 | tr ' ' '
 ' | sed 's/^.*[[]\(.*\)[]].*$/\1/' | grep '[0-9]' | sort -nu | tr -d '
 ' | ${EGREP} '^3$' > /dev/null || \
         { echo "kadmin pruneall failed $?"; cat messages.log ; exit 1; }
 
+env KRB5CCNAME=${cache} \
+    ${kadmin} -p foo/admin@${R} list --upto=3 '*' > kadmin.tmp
+[ `wc -l < kadmin.tmp` -eq 3 ] ||
+        { echo "kadmin list --upto 3 produced `wc -l < kadmin.tmp` results!"; exit 1; }
+
+#----------------------------------
+echo "kadmin get '*' (re-entrance)"; > messages.log
+${kadmin} -l get '*' > kadmin.tmp ||
+    { echo "failed to list principals"; cat messages.log ; exit 1; }
+> messages.log
+env KRB5CCNAME=${cache} \
+    ${kadmin} -p foo/admin@${R} get '*' > kadmin.tmp2 ||
+        { echo "failed to list principals"; cat messages.log ; exit 1; }
+diff -u kadmin.tmp kadmin.tmp2 ||
+    { echo "local and remote get all differ"; exit 1; }
+
+#----------------------------------
+# We have 20 principals in the DB.  Test two chunks of 1 (since that's how we
+# started kadmind above.
+> messages.log
+echo "kadmin list all (chunk size 1)"
+# Check that list produces the same output locally and remote.
+env KRB5CCNAME=${cache} \
+    ${kadmin} -p foo/admin@${R} list '*' | sort > kadmin.tmp ||
+        { echo "failed to list principals"; cat messages.log ; exit 1; }
+${kadmin} -l list '*' | sort > kadmin.tmp2
+diff kadmin.tmp kadmin.tmp2 ||
+        { echo "failed to list all principals"; cat messages.log ; exit 1; }
+# kadmin dump does not use kadm5_iter_principals, so this is a good way to
+# double check the above results.  This time we drop the realm part because
+# kadmin doesn't show us the realm for principals in the default realm.
+${kadmin} -l list '*' | cut -d'@' -f1 | sort > kadmin.tmp
+${kadmin} -l dump | cut -d'@' -f1 | sort > kadmin.tmp2
+diff kadmin.tmp kadmin.tmp2 ||
+        { echo "failed to list all principals (dump)"; cat messages.log ; exit 1; }
+${kadmin} -l > kadmin.tmp <<"EOF"
+list *
+get foo/admin
+EOF
+grep Attributes kadmin.tmp > /dev/null ||
+        { echo "failed to execute command after list"; cat messages.log ; exit 1; }
+env KRB5CCNAME=${cache} \
+${kadmin} -p foo/admin@${R} > kadmin.tmp <<"EOF"
+list *
+get foo/admin
+EOF
+grep Attributes kadmin.tmp > /dev/null ||
+        { echo "failed to execute command after list"; cat messages.log ; exit 1; }
+
+#----------------------------------
+# We have 20 principals in the DB.  Test two chunks of 10.
+sh ${leaks_kill} kadmind $kadmpid || exit 1
+${kadmind} --list-chunk-size=10 --detach
+kadmpid=`getpid kadmind`
+
+> messages.log
+echo "kadmin list all (chunk size 10)"
+# Check that list produces the same output locally and remote.
+env KRB5CCNAME=${cache} \
+    ${kadmin} -p foo/admin@${R} list '*' | sort > kadmin.tmp ||
+        { echo "failed to list principals"; cat messages.log ; exit 1; }
+${kadmin} -l list '*' | sort > kadmin.tmp2
+diff kadmin.tmp kadmin.tmp2 ||
+        { echo "failed to list all principals"; cat messages.log ; exit 1; }
+# kadmin dump does not use kadm5_iter_principals, so this is a good way to
+# double check the above results.  This time we drop the realm part because
+# kadmin doesn't show us the realm for principals in the default realm.
+${kadmin} -l list '*' | cut -d'@' -f1 | sort > kadmin.tmp
+${kadmin} -l dump | cut -d'@' -f1 | sort > kadmin.tmp2
+diff kadmin.tmp kadmin.tmp2 ||
+        { echo "failed to list all principals (dump)"; cat messages.log ; exit 1; }
+env KRB5CCNAME=${cache} \
+${kadmin} -p foo/admin@${R} > kadmin.tmp <<"EOF"
+list *
+get foo/admin
+EOF
+grep Attributes kadmin.tmp > /dev/null ||
+        { echo "failed to execute command after list"; cat messages.log ; exit 1; }
+
+#----------------------------------
+# We have 20 principals in the DB.  Test one chunk of 50.
+sh ${leaks_kill} kadmind $kadmpid || exit 1
+${kadmind} --list-chunk-size=50 --detach
+kadmpid=`getpid kadmind`
+
+> messages.log
+echo "kadmin list all (chunk size 50)"
+# Check that list produces the same output locally and remote.
+env KRB5CCNAME=${cache} \
+    ${kadmin} -p foo/admin@${R} list '*' | sort > kadmin.tmp ||
+        { echo "failed to list principals"; cat messages.log ; exit 1; }
+${kadmin} -l list '*' | sort > kadmin.tmp2
+diff kadmin.tmp kadmin.tmp2 ||
+        { echo "failed to list all principals"; cat messages.log ; exit 1; }
+# kadmin dump does not use kadm5_iter_principals, so this is a good way to
+# double check the above results.  This time we drop the realm part because
+# kadmin doesn't show us the realm for principals in the default realm.
+${kadmin} -l list '*' | cut -d'@' -f1 | sort > kadmin.tmp
+${kadmin} -l dump | cut -d'@' -f1 | sort > kadmin.tmp2
+diff kadmin.tmp kadmin.tmp2 ||
+        { echo "failed to list all principals (dump)"; cat messages.log ; exit 1; }
+env KRB5CCNAME=${cache} \
+${kadmin} -p foo/admin@${R} > kadmin.tmp <<"EOF"
+list *
+get foo/admin
+EOF
+grep Attributes kadmin.tmp > /dev/null ||
+        { echo "failed to execute command after list"; cat messages.log ; exit 1; }
+
+#----------------------------------
+# We have 20 principals in the DB.  Test 3 chunks of up to 7.
+sh ${leaks_kill} kadmind $kadmpid || exit 1
+${kadmind} --list-chunk-size=7 --detach
+kadmpid=`getpid kadmind`
+
+> messages.log
+echo "kadmin list all (chunk size 7)"
+# Check that list produces the same output locally and remote.
+env KRB5CCNAME=${cache} \
+    ${kadmin} -p foo/admin@${R} list '*' | sort > kadmin.tmp ||
+        { echo "failed to list principals"; cat messages.log ; exit 1; }
+${kadmin} -l list '*' | sort > kadmin.tmp2
+diff kadmin.tmp kadmin.tmp2 ||
+        { echo "failed to list all principals"; cat messages.log ; exit 1; }
+# kadmin dump does not use kadm5_iter_principals, so this is a good way to
+# double check the above results.  This time we drop the realm part because
+# kadmin doesn't show us the realm for principals in the default realm.
+${kadmin} -l list '*' | cut -d'@' -f1 | sort > kadmin.tmp
+${kadmin} -l dump | cut -d'@' -f1 | sort > kadmin.tmp2
+diff kadmin.tmp kadmin.tmp2 ||
+        { echo "failed to list all principals (dump)"; cat messages.log ; exit 1; }
+
 #----------------------------------
 
 echo "killing kdc (${kdcpid} ${kadmpid})"
 sh ${leaks_kill} kdc $kdcpid || exit 1
+sh ${leaks_kill} kadmind $kadmpid || exit 1
 
 trap "" EXIT
 
index e53293b2427f3565a6475ba3187b32075ee6f1e6..7d2f4edc7f363267e2de921b7f1a4b3e74143721 100644 (file)
@@ -238,11 +238,11 @@ ${kadmin} ext -k ${keytab} ${rps} || exit 1
 
 ${kadmin} add -p kaka --use-defaults ${server2}@${R2} || exit 1
 ${kadmin} ext -k ${keytab} ${server2}@${R2} || exit 1
-${kadmin} add -p foo  --use-defaults referral-placeholder@${R5} || exit 1
-${kadmin} add_alias referral-placeholder@${R5} ${server3}@${R} || exit 1
+${kadmin} add -p foo  --use-defaults WELLKNOWN/REFERRALS/TARGET@${R5} || exit 1
+${kadmin} add_alias WELLKNOWN/REFERRALS/TARGET@${R5} ${server3}@${R} || exit 1
 ${kadmin5} add -p kaka --use-defaults ${server3}@${R5} || exit 1
 ${kadmin5} ext -k ${keytab} ${server3}@${R5} || exit 1
-${kadmin} add_alias referral-placeholder@${R5} ${namespace}@${R} || exit 1
+${kadmin} add_alias WELLKNOWN/REFERRALS/TARGET@${R5} ${namespace}@${R} || exit 1
 ${kadmin5} add -p kaka --use-defaults ${server5}@${R5} || exit 1
 ${kadmin5} ext -k ${keytab} ${server5}@${R5} || exit 1
 ${kadmin} add -p kaka --use-defaults ${serverip}@${R} || exit 1
index 73c26c368caa3b41102d23ba4a24c20ba8a9732d..49f6a52e449362f467730dbe557717f6477befc7 100644 (file)
@@ -42,10 +42,24 @@ testfailed="echo test failed; cat messages.log; exit 1"
 # If there is no useful db support compiled in, disable test
 ${have_db} || exit 77
 
+d=test.h5l.se
+d2=xtst.heim.example
 R=TEST.H5L.SE
-R2=SUB.TEST.H5L.SE
-
-service=ldap/host.sub.test.h5l.se:389
+R2=XTST.HEIM.EXAMPLE
+
+# $service1 will be a hard alias of $service2
+service1=ldap/host.${d}:389
+service2=ldap/host.${d2}:389
+# $service3 and $service4 will have soft aliases referrals from each
+# other's realms
+service3=host/foohost.${d}
+service4=host/barhost.${d2}
+# $service5 and $service6 will be hardaliases
+service5=host/thing1.${d}
+service6=host/thing1.${d2}
+# $service7 and $service8 will be hardaliases in the opposite direction
+service7=host/thing2.${d}
+service8=host/thing2.${d2}
 
 port=@port@
 
@@ -64,6 +78,9 @@ keytab="FILE:${keytabfile}"
 KRB5_CONFIG="${objdir}/krb5.conf"
 export KRB5_CONFIG
 
+KRB5CCNAME=$cache
+export KRB5CCNAME
+
 rm -f ${keytabfile}
 rm -f current-db*
 rm -f out-*
@@ -84,11 +101,34 @@ ${kadmin} \
     --realm-max-renewable-life=1month \
     ${R2} || exit 1
 
+${kadmin} add -r --use-defaults WELLKNOWN/REFERRALS/TARGET@${R} || exit 1
+${kadmin} add -r --use-defaults WELLKNOWN/REFERRALS/TARGET@${R2} || exit 1
+
+# User 'foo' gets two aliases in the same realm, and one in the other
 ${kadmin} add -p foo --use-defaults foo@${R} || exit 1
-${kadmin} modify --alias=alias1 --alias=alias2 foo@${R} || exit 1
+${kadmin} add_alias foo@${R} foo@${R2} alias1 alias2 || exit 1
 ${kadmin} get foo@${R} | grep alias1@${R} >/dev/null || exit 1
+${kadmin} get foo@${R} | grep alias2@${R} >/dev/null || exit 1
+${kadmin} get foo@${R} | grep foo@${R2} >/dev/null || exit 1
+
+# service1 is an alias of service2, in different realms
+${kadmin} add -p foo --use-defaults    ${service2}@${R2} || exit 1
+${kadmin} add_alias ${service2}@${R2}  ${service1}@${R}  || exit 1
+${kadmin} get ${service2}@${R2} | grep ${service1}@${R} >/dev/null || exit 1
+
+# service3 and service4 get soft aliases in each other's realms
+${kadmin} add -p foo --use-defaults  ${service3}@${R}  || exit 1
+${kadmin} add -p foo --use-defaults  ${service4}@${R2} || exit 1
+${kadmin} add_alias WELLKNOWN/REFERRALS/TARGET@${R2} ${service4}@${R} || exit 1
+${kadmin} add_alias WELLKNOWN/REFERRALS/TARGET@${R}  ${service3}@${R2} || exit 1
 
-${kadmin} add -p foo --use-defaults  ${service}@${R2} || exit 1
+# service6 is a hard alias of service5
+${kadmin} add -p foo --use-defaults  ${service5}@${R}  || exit 1
+${kadmin} add_alias ${service5}@${R} ${service6}@${R2} || exit 1
+
+# service8 is a hard alias of service7, but in the opposite direction
+${kadmin} add -p foo --use-defaults  ${service7}@${R2} || exit 1
+${kadmin} add_alias ${service5}@${R} ${service8}@${R}  || exit 1
 
 ${kadmin} add -p foo --use-defaults bar@${R} || exit 1
 ${kadmin} add -p foo --use-defaults 'baz\@realm.foo@'${R} || exit 1
@@ -187,6 +227,31 @@ ${klist} | grep "Principal: alias1@${R}" > /dev/null || \
 echo "checking that we got back right principal inside the PAC"
 ${test_ap_req} krbtgt/${R}@${R} ${keytab} ${cache} || \
        { ec=1 ; eval "${testfailed}"; }
+${kgetcred} ${service2}@${R2} || { ec=1 ; eval "${testfailed}"; }
+${kgetcred} ${service1}@${R} || { ec=1 ; eval "${testfailed}"; }
+${kdestroy}
+
+echo "Getting client foo@${R2} tickets (non canon case)"; > messages.log
+${kinit} --password-file=${objdir}/foopassword foo@${R2} || \
+       { ec=1 ; eval "${testfailed}"; }
+echo "checking that we got back right principal"
+${klist} | grep "Principal: foo@${R2}" > /dev/null || \
+       { ec=1 ; eval "${testfailed}"; }
+echo "checking that we got back right principal inside the PAC"
+${test_ap_req} krbtgt/${R}@${R} ${keytab} ${cache} || \
+       { ec=1 ; eval "${testfailed}"; }
+echo "Getting various service tickets using foo@${R2} client"
+${kgetcred} ${service2}@${R2} || { ec=1 ; eval "${testfailed}"; }
+${kgetcred} ${service1}@${R}  || { ec=1 ; eval "${testfailed}"; }
+${kgetcred} ${service1}@${R2} || { ec=1 ; eval "${testfailed}"; }
+${kgetcred} ${service2}@${R}  || { ec=1 ; eval "${testfailed}"; }
+${kgetcred} ${service3}@      || { ec=1 ; eval "${testfailed}"; }
+${kgetcred} ${service4}@      || { ec=1 ; eval "${testfailed}"; }
+${kgetcred} ${service5}@      || { ec=1 ; eval "${testfailed}"; }
+${kgetcred} ${service6}@      || { ec=1 ; eval "${testfailed}"; }
+${kgetcred} ${service7}@      || { ec=1 ; eval "${testfailed}"; }
+${kgetcred} ${service8}@${R}  || { ec=1 ; eval "${testfailed}"; }
+${kdestroy}
 
 echo "Getting client alias2 tickets (removed)"; > messages.log
 ${kadmin} modify --alias=alias1 foo@${R} || { ec=1 ; eval "${testfailed}"; }
@@ -200,25 +265,30 @@ ${kadmin} modify --alias= foo@${R} || { ec=1 ; eval "${testfailed}"; }
 
 echo "Test server referrals"
 
-echo "Getting client for ${service}@${R} (tgs kdc referral)"
+echo "Getting client for ${service2}@${R} (tgs kdc referral)"
 > messages.log
 ${kinit} --password-file=${objdir}/foopassword foo@${R} || \
        { ec=1 ; eval "${testfailed}"; }
-${kgetcred} --canonicalize ${service}@${R} ||
-       { ec=1 ; eval "${testfailed}"; }
+${kgetcred} --canonicalize ${service2}@${R} || { ec=1 ; eval "${testfailed}"; }
+${kgetcred} ${service3}@${R} || { ec=1 ; eval "${testfailed}"; }
+${kgetcred} ${service4}@ || { ec=1 ; eval "${testfailed}"; }
 echo "checking that we got back right principal"
-${klist} | grep "${service}@${R2}" > /dev/null || \
+${klist} | grep "${service2}@${R2}" > /dev/null || \
+       { ec=1 ; eval "${testfailed}"; }
+${klist} | grep "${service4}@${R}" > /dev/null && \
+       { ec=1 ; eval "${testfailed}"; }
+${klist} | grep "${service4}@${R2}" > /dev/null || \
        { ec=1 ; eval "${testfailed}"; }
 ${kdestroy}
 
-echo "Getting client for ${service}@${R2} (tgs client side guessing)"
+echo "Getting client for ${service2}@${R2} (tgs client side guessing)"
 > messages.log
 ${kinit} --password-file=${objdir}/foopassword foo@${R} || \
        { ec=1 ; eval "${testfailed}"; }
-${kgetcred} ${service}@${R2} ||
+${kgetcred} ${service2}@${R2} ||
        { ec=1 ; eval "${testfailed}"; }
 echo "checking that we got back right principal"
-${klist} | grep "${service}@${R2}" > /dev/null || \
+${klist} | grep "${service2}@${R2}" > /dev/null || \
        { ec=1 ; eval "${testfailed}"; }
 ${kdestroy}
 
index 4882d52f5e95c21c7f56ed7c861040dd6b0dd3b1..f887e82c4f1d7a2b1545ff9840953f265391b294 100644 (file)
  
 [ext_keytab]
         simple_csr_authorizer_directory = @objdir@/simple_csr_authz
+        new_hostbased_service_principal_attributes = {
+            host = {
+                a-particular-hostname.test.h5l.se = ok-as-delegate,no-auth-data-reqd
+                .prod.test.h5l.se = ok-as-delegate no-auth-data-reqd
+            }
+        }
 
 [logging]
        kdc = 0-/FILE:@objdir@/messages.log
index a85836d76b2439f0c2aeef484ef21d08ae310e59..5b9d644cd0accc8a08f08d003f3698207e070072 100644 (file)
@@ -31,6 +31,9 @@
        TEST4.H5L.SE = {
                kdc = localhost:@port@
        }
+        XTST.HEIM.EXAMPLE = {
+               kdc = localhost:@port@
+        }
        SOME-REALM5.FR = {
                kdc = localhost:@port@
        }
index 0c8614176d85de3e87b644210a82327608542e23..f58dbf7779387d8f8d96e1c64aaec85a109fd81e 100644 (file)
@@ -1,14 +1,14 @@
 Building Heimdal for Windows
 ===================
 
-1. Introduction
----------------
+1. Introduction
+
 
 Heimdal can be built and run on Windows XP or later.  Older OSs may
 work, but have not been tested.
 
-2. Prerequisites
-----------------
+2. Prerequisites
+
 
 * __Microsoft Visual C++ Compiler__: Heimdal has been tested with
   Microsoft Visual C/C++ compiler version 15.x.  This corresponds to
@@ -25,7 +25,8 @@ work, but have not been tested.
 * __Perl__: A recent version of Perl.  Tested with ActiveState
   ActivePerl.
 
-* __Python__: Tested with Python 2.5 and 2.6.
+* __Python__: Tested with Python 2.5 and 2.6.  Python 3.9 is known to not
+  work.
 
 * __WiX__: The Windows [Installer XML toolkit (WiX)][1] Version 3.x is
   used to build the installers.
@@ -37,6 +38,8 @@ work, but have not been tested.
   However, a recent build of `makeinfo` is required for building the
   documentation. Cygwin makeinfo 4.7 is known to work.
 
+  - Native `makeinfo.exe` is no longer available from cygwin.
+
 * __Certificate for code-signing__: The Heimdal build produces a
   number of Assemblies that should be signed if they are to be
   installed via Windows Installer.  In addition, all executable
@@ -48,14 +51,14 @@ work, but have not been tested.
 
 [1]: http://wix.sourceforge.net/
 
-3. Setting up the build environment
------------------------------------
+3. Setting up the build environment
+
 
-* Start with a Windows SDK or Visual Studio build environment.  The
+* Starting with a Windows SDK environment:  The
   target platform, OS and build type (debug / release) is determined
   by the build environment.
 
-  E.g.: If you are using the Windows SDK, you can use the `SetEnv.Cmd`
+  E.g.: You can use the `SetEnv.Cmd`
   script to set up a build environment targetting 64-bit Windows XP or
   later with:
 
@@ -67,6 +70,16 @@ work, but have not been tested.
 
   the build will produce release binaries.
 
+*  Starting with a Visual Studio build: The target platform and OS is determined
+   by the build environment.
+
+   E.g.: You can use the `vcvarsall.bat` script to set up an environ,ent
+   script to set up a build environment targetting 64-bit Windows 10 with:
+
+       vcvarsall.bat x64 10.0.19041.0  -vcvars_ver=14.29 -vcvars_spectre_libs=spectre
+
+   The choice of Debug or Release is made on the `nmake` command line.
+
 * Add any directories to `PATH` as necessary for tools required by
   the build to be found.  The build scripts will check for build
   tools at the start of the build and will indicate which ones are
@@ -114,6 +127,11 @@ work, but have not been tested.
         set CODESIGN=c:\scripts\mycodesigner.cmd
        set CODESIGN_SHA256=c:\scripts\mycodesigner256.cmd
 
+   - 'APPVER'.  This environment variable controls the version passed to
+     the `-subsystem` qualifier for linker.  Additionally it helps
+     locate the runtime library (or otherwise) associated with the
+     compiler (Not sure how to build for XP with VC2017)
+
 * Define the code sign public key token.  This is contained in the
   environment variable `CODESIGN_PKT` and is needed to build the
   Heimdal assemblies.  If you are not using a code-sign certificate,
@@ -133,15 +151,21 @@ work, but have not been tested.
 
       set CODESIGN_PKT=abcdef0123456789
 
-4. Running the build
---------------------
+# 4. Running the build
+
+_If you checkout from git, you should ensure that and `awk` input files
+retain unix (LF only) line endings._
 
 Change the current directory to the root of the Heimdal source tree
 and run:
 
     nmake /f NTMakefile
 
-This should build the binaries, assemblies and the installers.
+This should build the binaries, assemblies and the installers.  If you are
+building with the Visual Studio tools you can build the release versions
+by setting the NODEBUG variable
+
+    nmake /f NTMakefile NODEBUG=TRUE
 
 The build can also be invoked from any subdirectory that contains an
 `NTMakefile` using the same command.  Keep in mind that there are
@@ -163,3 +187,16 @@ be built.  First build for X86 and then build AMD64
     nmake /f NTMakefile MULTIPLATFORM_INSTALLER=1
 
 The build must be executed under cmd.exe.
+
+# 5. Makeinfo
+
+Makeinfo is no longer available from cygwin (see
+[this mail thread][2]).The following appears to work
+(added to NTMakefile.w32)
+
+    MAKEINFO=$(PERL) C:\cygwin64\bin\texi2any
+
+You should expect a certain amount of debugging to ensure that all the required
+Perl libraries are installed.
+
+[2]: https://sourceware.org/legacy-ml/cygwin/2015-03/msg00503.html
\ No newline at end of file