New implementation of transited-realm checking, with some test cases. The test
authorKen Raeburn <raeburn@mit.edu>
Wed, 20 Jun 2001 04:07:43 +0000 (04:07 +0000)
committerKen Raeburn <raeburn@mit.edu>
Wed, 20 Jun 2001 04:07:43 +0000 (04:07 +0000)
cases currently check only t-r list expansion, not the validation step.

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

src/lib/krb5/krb/ChangeLog
src/lib/krb5/krb/Makefile.in
src/lib/krb5/krb/chk_trans.c
src/lib/krb5/krb/t_krb5.conf
src/lib/krb5/krb/transit-tests [new file with mode: 0644]

index 8f38c32c2456d3a8575d83435218a8510b68da9e..4c86bb10703230da1b3ca046f6f225bc33af97db 100644 (file)
@@ -1,3 +1,14 @@
+2001-06-19  Ken Raeburn  <raeburn@mit.edu>
+
+       * chk_trans.c: Reimplemented from scratch.
+       * transit-tests: New file.
+       * Makefile.in (t_expand, t_expand.o): New targets.  Build test
+       program from chk_trans.c.
+       (T_EXPAND_OBJS): New variable.
+       (TEST_PROGS): Add t_expand.
+       (check-unix): Run transit-tests.
+       * t_krb5.conf: Added capaths section.
+
 2001-06-16  Ken Raeburn  <raeburn@mit.edu>
 
        * fwd_tgt.c (krb5_fwd_tgt_creds): Copy enctype for new creds from
index 641817854f3523177137b16e009fa8e0b7ad809b..c07bd47964a49a86f99b27657dae2a395e56cfa9 100644 (file)
@@ -304,7 +304,13 @@ t_ser: $(T_SER_OBJS) $(KDB5_DEPLIBS) $(KRB5_BASE_DEPLIBS)
 t_deltat : $(T_DELTAT_OBJS)
        $(CC_LINK) -o t_deltat $(T_DELTAT_OBJS)
 
-TEST_PROGS= t_walk_rtree t_kerb t_ser t_deltat
+T_EXPAND_OBJS=t_expand.o
+t_expand.o : chk_trans.c
+       $(CC) $(ALL_CFLAGS) -c -DTEST -o t_expand.o $(srcdir)/chk_trans.c
+t_expand : $(T_EXPAND_OBJS) $(KRB5_BASE_DEPLIBS)
+       $(CC_LINK) -o t_expand $(T_EXPAND_OBJS) $(KRB5_BASE_LIBS)
+
+TEST_PROGS= t_walk_rtree t_kerb t_ser t_deltat t_expand
 
 check-unix:: $(TEST_PROGS)
        KRB5_CONFIG=$(srcdir)/t_krb5.conf ; export KRB5_CONFIG ;\
@@ -334,6 +340,7 @@ check-unix:: $(TEST_PROGS)
        KRB5_CONFIG=$(srcdir)/t_krb5.conf ; export KRB5_CONFIG ;\
                $(RUN_SETUP) ./t_ser
        ./t_deltat
+       sh $(srcdir)/transit-tests
 
 clean::
        $(RM) $(OUTPRE)t_walk_rtree$(EXEEXT) $(OUTPRE)t_walk_rtree.$(OBJEXT) \
index 357c43848956bca08212a5d2d4e40b74ec155f7a..87a32c2bc52e46399c104ed29c7588239c8cbfed 100644 (file)
@@ -1,12 +1,14 @@
 /*
- * Copyright (c) 1994 CyberSAFE Corporation.
- * All rights reserved.
+ * lib/krb5/krb/chk_trans.c
+ *
+ * Copyright 2001 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
  *
  * Export of this software from the United States of America may
  *   require a specific license from the United States Government.
  *   It is the responsibility of any person or organization contemplating
  *   export to obtain such a license before exporting.
- *
+ * 
  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
  * distribute this software and its documentation for any purpose and
  * without fee is hereby granted, provided that the above copyright
  * this permission notice appear in supporting documentation, and that
  * the name of M.I.T. not be used in advertising or publicity pertaining
  * to distribution of the software without specific, written prior
- * permission.  Neither M.I.T., the Open Computing Security Group, nor
- * CyberSAFE Corporation make any representations about the suitability of
+ * permission.  Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
  * this software for any purpose.  It is provided "as is" without express
  * or implied warranty.
+ * 
+ *
+ * krb5_check_transited_list()
  */
-
 #include "k5-int.h"
-#include <stdio.h>
+#include <stdarg.h>
 
-#define MAX_REALM_LN 500
+#if defined (TEST) || defined (TEST2)
+# undef DEBUG
+# define DEBUG
+#endif
 
-krb5_error_code
-krb5_check_transited_list(context, trans, realm1, realm2)
-    krb5_context context;
-    krb5_data      *trans;
-    krb5_data      *realm1;
-    krb5_data      *realm2;
-{
-    char            prev[MAX_REALM_LN+1];
-    char            next[MAX_REALM_LN+1];
-    char            *nextp;
-    int             i, j;
-    int             trans_length;
-    krb5_error_code retval = 0;
-    krb5_principal  *tgs_list;
-
-    if (trans == NULL || trans->data == NULL || trans->length == 0)
-       return(0);
-    trans_length = trans->data[trans->length-1] ?
-       trans->length : trans->length - 1;
-
-    for (i = 0; i < trans_length; i++)
-       if (trans->data[i] == '\0') {
-           /* Realms may not contain ASCII NUL character. */
-           return(KRB5KRB_AP_ERR_ILL_CR_TKT);
-       }
+#ifdef DEBUG
+#define verbose krb5int_chk_trans_verbose
+static int verbose = 0;
+# define Tprintf(ARGS) if (verbose) printf ARGS
+#else
+# define Tprintf(ARGS) (void)(0)
+#endif
+
+#define MAXLEN 512
+
+static krb5_error_code
+process_intermediates (krb5_error_code (*fn)(krb5_data *, void *), void *data,
+                      const krb5_data *n1, const krb5_data *n2) {
+    unsigned int len1, len2, i;
+    char *p1, *p2;
+
+    Tprintf (("process_intermediates(%.*s,%.*s)\n",
+             n1->length, n1->data, n2->length, n2->data));
 
-    if ((retval = krb5_walk_realm_tree(context, realm1, realm2, &tgs_list,
-                                      KRB5_REALM_BRANCH_CHAR))) {
-       return(retval);
+    len1 = n1->length;
+    len2 = n2->length;
+
+    Tprintf (("(walking intermediates now)\n"));
+    /* Simplify...  */
+    if (len1 > len2) {
+       const krb5_data *p;
+       int tmp = len1;
+       len1 = len2;
+       len2 = tmp;
+       p = n1;
+       n1 = n2;
+       n2 = p;
+    }
+    /* Okay, now len1 is always shorter or equal.  */
+    if (len1 == len2) {
+       if (memcmp (n1->data, n2->data, len1)) {
+           Tprintf (("equal length but different strings in path: '%.*s' '%.*s'\n",
+                     n1->length, n1->data, n2->length, n2->data));
+           return KRB5KRB_AP_ERR_ILL_CR_TKT;
+       }
+       Tprintf (("(end intermediates)\n"));
+       return 0;
     }
+    /* Now len1 is always shorter.  */
+    if (len1 == 0)
+       /* Shouldn't be possible.  Internal error?  */
+       return KRB5KRB_AP_ERR_ILL_CR_TKT;
+    p1 = n1->data;
+    p2 = n2->data;
+    if (p1[0] == '/') {
+       /* X.500 style names, with common prefix.  */
+       if (p2[0] != '/') {
+           Tprintf (("mixed name formats in path: x500='%.*s' domain='%.*s'\n",
+                     len1, p1, len2, p2));
+           return KRB5KRB_AP_ERR_ILL_CR_TKT;
+       }
+       if (memcmp (p1, p2, len1)) {
+           Tprintf (("x500 names with different prefixes '%.*s' '%.*s'\n",
+                     len1, p1, len2, p2));
+           return KRB5KRB_AP_ERR_ILL_CR_TKT;
+       }
+       for (i = len1 + 1; i < len2; i++)
+           if (p2[i] == '/') {
+               krb5_data d;
+               krb5_error_code r;
 
-    memset(prev, 0, sizeof(prev));
-    memset(next, 0, sizeof(next)), nextp = next;
-    for (i = 0; i < trans_length; i++) {
-       if (i < trans_length-1 && trans->data[i] == '\\') {
-           i++;
-           *nextp++ = trans->data[i];
-           if (nextp - next >= sizeof(next)) {
-               retval = KRB5KRB_AP_ERR_ILL_CR_TKT;
-               goto finish;
+               d.data = p2;
+               d.length = i;
+               r = (*fn) (&d, data);
+               if (r)
+                   return r;
            }
-           continue;
+    } else {
+       /* Domain style names, with common suffix.  */
+       if (p2[0] == '/') {
+           Tprintf (("mixed name formats in path: domain='%.*s' x500='%.*s'\n",
+                     len1, p1, len2, p2));
+           return KRB5KRB_AP_ERR_ILL_CR_TKT;
        }
-       if (i < trans_length && trans->data[i] != ',') {
-           *nextp++ = trans->data[i];
-           if (nextp - next >= sizeof(next)) {
-               retval = KRB5KRB_AP_ERR_ILL_CR_TKT;
-               goto finish;
+       if (memcmp (p1, p2 + (len2 - len1), len1)) {
+           Tprintf (("domain names with different suffixes '%.*s' '%.*s'\n",
+                     len1, p1, len2, p2));
+           return KRB5KRB_AP_ERR_ILL_CR_TKT;
+       }
+       for (i = len2 - len1 - 1; i > 0; i--) {
+           Tprintf (("looking at '%.*s'\n", len2 - i, p2+i));
+           if (p2[i-1] == '.') {
+               krb5_data d;
+               krb5_error_code r;
+
+               d.data = p2+i;
+               d.length = len2 - i;
+               r = (*fn) (&d, data);
+               if (r)
+                   return r;
            }
-           continue;
        }
-       next[sizeof(next) - 1] = '\0';
-       if (strlen(next) > 0) {
-           if (next[0] != '/') {
-               if (*(nextp-1) == '.' && strlen(next) + strlen(prev) <= MAX_REALM_LN)
-                   strncat(next, prev, sizeof(next) - 1 - strlen(next));
-               retval = KRB5KRB_AP_ERR_ILL_CR_TKT;
-               for (j = 0; tgs_list[j]; j++) {
-                   if (strlen(next) == (size_t) krb5_princ_realm(context, tgs_list[j])->length &&
-                       !memcmp(next, krb5_princ_realm(context, tgs_list[j])->data,
-                               strlen(next))) {
-                       retval = 0;
-                       break; 
+    }
+    Tprintf (("(end intermediates)\n"));
+    return 0;
+}
+
+static krb5_error_code
+maybe_join (krb5_data *last, krb5_data *buf, int bufsiz)
+{
+    if (buf->length == 0)
+       return 0;
+    if (buf->data[0] == '/') {
+       if (last->length + buf->length > bufsiz) {
+           Tprintf (("too big: last=%d cur=%d max=%d\n", last->length, buf->length, bufsiz));
+           return KRB5KRB_AP_ERR_ILL_CR_TKT;
+       }
+       memmove (buf->data+last->length, buf->data, buf->length);
+       memcpy (buf->data, last->data, last->length);
+       buf->length += last->length;
+    } else if (buf->data[buf->length-1] == '.') {
+       /* We can ignore the case where the previous component was
+          empty; the strcat will be a no-op.  It should probably
+          be an error case, but let's be flexible.  */
+       if (last->length+buf->length > bufsiz) {
+           Tprintf (("too big\n"));
+           return KRB5KRB_AP_ERR_ILL_CR_TKT;
+       }
+       memcpy (buf->data + buf->length, last->data, last->length);
+       buf->length += last->length;
+    }
+    /* Otherwise, do nothing.  */
+    return 0;
+}
+
+/* The input strings cannot contain any \0 bytes, according to the
+   spec, but our API is such that they may not be \0 terminated
+   either.  Thus we keep on treating them as krb5_data objects instead
+   of C strings.  */
+static krb5_error_code
+foreach_realm (krb5_error_code (*fn)(krb5_data *comp,void *data), void *data,
+              const krb5_data *crealm, const krb5_data *srealm,
+              const krb5_data *transit)
+{
+    char buf[MAXLEN], last[MAXLEN];
+    char *p, *bufp;
+    int next_lit, have_prev, intermediates, l;
+    krb5_data this_component;
+    krb5_error_code r;
+    krb5_data last_component;
+
+    /* Invariants:
+       - last_component points to last[]
+       - this_component points to buf[]
+       - last_component has length of last
+       - this_component has length of buf when calling out
+       Keep these consistent, and we should be okay.  */
+
+    next_lit = 0;
+    have_prev = 1;
+    intermediates = 0;
+    memset (buf, 0, sizeof (buf));
+
+    this_component.data = buf;
+    last_component.data = last;
+    last_component.length = 0;
+
+#define print_data(fmt,d) Tprintf((fmt,(int)(d)->length,(d)->data))
+    print_data ("client realm: %.*s\n", crealm);
+    print_data ("server realm: %.*s\n", srealm);
+    print_data ("transit enc.: %.*s\n", transit);
+
+    if (transit->length == 0) {
+       Tprintf (("no other realms transited\n"));
+       return 0;
+    }
+
+    bufp = buf;
+    for (p = transit->data, l = transit->length; l; p++, l--) {
+       if (next_lit) {
+           *bufp++ = *p;
+           if (bufp == buf+sizeof(buf))
+               return KRB5KRB_AP_ERR_ILL_CR_TKT;
+           next_lit = 0;
+       } else if (*p == '\\') {
+           next_lit = 1;
+       } else if (*p == ',') {
+           if (bufp != buf) {
+               this_component.length = bufp - buf;
+               r = maybe_join (&last_component, &this_component, sizeof(buf));
+               if (r)
+                   return r;
+               r = (*fn) (&this_component, data);
+               if (r)
+                   return r;
+               if (intermediates) {
+                   if (p == transit->data)
+                       r = process_intermediates (fn, data,
+                                                  &this_component, crealm);
+                   else {
+                       r = process_intermediates (fn, data, &this_component,
+                                                  &last_component);
                    }
+                   if (r)
+                       return r;
+               }
+               intermediates = 0;
+               have_prev = 1;
+               memcpy (last, buf, sizeof (buf));
+               last_component.length = this_component.length;
+               memset (buf, 0, sizeof (buf));
+               bufp = buf;
+           } else {
+               intermediates = 1;
+               if (p == transit->data) {
+                   if (crealm->length >= MAXLEN)
+                       return KRB5KRB_AP_ERR_ILL_CR_TKT;
+                   memcpy (last, crealm->data, crealm->length);
+                   last[crealm->length] = '\0';
+                   last_component.length = crealm->length;
                }
-               if (retval)  goto finish;
-           }
-           if (i+1 < trans_length && trans->data[i+1] == ' ') {
-               i++;
-               memset(next, 0, sizeof(next)), nextp = next;
-               continue;
-           }
-           if (i+1 < trans_length && trans->data[i+1] != '/') {
-               strncpy(prev, next, sizeof(prev) - 1);
-               memset(next, 0, sizeof(next)), nextp = next;
-               continue;
            }
+       } else if (*p == ' ' && bufp == buf) {
+           /* This next component stands alone, even if it has a
+              trailing dot or leading slash.  */
+           memset (last, 0, sizeof (last));
+           last_component.length = 0;
+       } else {
+           /* Not a special character; literal.  */
+           *bufp++ = *p;
+           if (bufp == buf+sizeof(buf))
+               return KRB5KRB_AP_ERR_ILL_CR_TKT;
+       }
+    }
+    /* At end.  Must be normal state.  */
+    if (next_lit)
+       Tprintf (("ending in next-char-literal state\n"));
+    /* Process trailing element or comma.  */
+    if (bufp == buf) {
+       /* Trailing comma.  */
+       r = process_intermediates (fn, data, &last_component, srealm);
+    } else {
+       /* Trailing component.  */
+       this_component.length = bufp - buf;
+       r = maybe_join (&last_component, &this_component, sizeof(buf));
+       if (r)
+           return r;
+       r = (*fn) (&this_component, data);
+       if (r)
+           return r;
+       if (intermediates)
+           r = process_intermediates (fn, data, &this_component,
+                                      &last_component);
+    }
+    if (r != 0)
+       return r;
+    return 0;
+}
+
+\f
+struct check_data {
+    krb5_context ctx;
+    krb5_principal *tgs;
+};
+
+static int
+same_data (krb5_data *d1, krb5_data *d2)
+{
+    return (d1->length == d2->length
+           && !memcmp (d1->data, d2->data, d1->length));
+}
+
+static krb5_error_code
+check_realm_in_list (krb5_data *realm, void *data)
+{
+    struct check_data *cdata = data;
+    int i;
+
+    Tprintf ((".. checking '%.*s'\n", (int) realm->length, realm->data));
+    for (i = 0; cdata->tgs[i]; i++) {
+       if (same_data (krb5_princ_realm (cdata->ctx, cdata->tgs[i]), realm))
+           return 0;
+    }
+    Tprintf (("BAD!\n"));
+    return KRB5KRB_AP_ERR_ILL_CR_TKT;
+}
+
+krb5_error_code
+krb5_check_transited_list (krb5_context ctx, krb5_data *trans,
+                          krb5_data *crealm, krb5_data *srealm)
+{
+    struct check_data cdata;
+    krb5_error_code r;
+
+    Tprintf (("krb5_check_transited_list(trans=\"%.*s\", crealm=\"%.*s\", srealm=\"%.*s\")\n",
+             (int) trans->length, trans->data,
+             (int) crealm->length, crealm->data,
+             (int) srealm->length, srealm->data));
+    if (trans->length == 0)
+       return 0;
+    r = krb5_walk_realm_tree (ctx, crealm, srealm, &cdata.tgs,
+                             KRB5_REALM_BRANCH_CHAR);
+    if (r) {
+       Tprintf (("error %ld\n", (long) r));
+       return r;
+    }
+#ifdef DEBUG /* avoid compiler warning about 'd' unused */
+    {
+       int i;
+       Tprintf (("tgs list = {\n"));
+       for (i = 0; cdata.tgs[i]; i++) {
+           char *name;
+           r = krb5_unparse_name (ctx, cdata.tgs[i], &name);
+           Tprintf (("\t'%s'\n", name));
+           free (name);
        }
+       Tprintf (("}\n"));
+    }
+#endif
+    cdata.ctx = ctx;
+    r = foreach_realm (check_realm_in_list, &cdata, crealm, srealm, trans);
+    krb5_free_realm_tree (ctx, cdata.tgs);
+    return r;
+}
+\f
+#ifdef TEST
+
+static krb5_error_code
+print_a_realm (krb5_data *realm, void *data)
+{
+    printf ("%.*s\n", realm->length, realm->data);
+    return 0;
+}
+
+int main (int argc, char *argv[]) {
+    const char *me;
+    krb5_data crealm, srealm, transit;
+    krb5_error_code r;
+    int expand_only = 0;
+
+    me = strrchr (argv[0], '/');
+    me = me ? me+1 : argv[0];
+
+    while (argc > 3 && argv[1][0] == '-') {
+       if (!strcmp ("-v", argv[1]))
+           verbose++, argc--, argv++;
+       else if (!strcmp ("-x", argv[1]))
+           expand_only++, argc--, argv++;
+       else
+           goto usage;
+    }
+
+    if (argc != 4) {
+    usage:
+       printf ("usage: %s [-v] [-x] clientRealm serverRealm transitEncoding\n",
+               me);
+       return 1;
     }
 
-finish:
-    krb5_free_realm_tree(context, tgs_list);
-    return(retval);
+    crealm.data = argv[1];
+    crealm.length = strlen(argv[1]);
+    srealm.data = argv[2];
+    srealm.length = strlen(argv[2]);
+    transit.data = argv[3];
+    transit.length = strlen(argv[3]);
+
+    if (expand_only) {
+
+       printf ("client realm: %s\n", argv[1]);
+       printf ("server realm: %s\n", argv[2]);
+       printf ("transit enc.: %s\n", argv[3]);
+
+       if (argv[3][0] == 0) {
+           printf ("no other realms transited\n");
+           return 0;
+       }
+
+       r = foreach_realm (print_a_realm, NULL, &crealm, &srealm, &transit);
+       if (r)
+           printf ("--> returned error %ld\n", (long) r);
+       return r != 0;
+
+    } else {
+
+       /* Actually check the values against the supplied krb5.conf file.  */
+       krb5_context ctx;
+       r = krb5_init_context (&ctx);
+       if (r) {
+           com_err (me, r, "initializing krb5 context");
+           return 1;
+       }
+       r = krb5_check_transited_list (ctx, &transit, &crealm, &srealm);
+       if (r == KRB5KRB_AP_ERR_ILL_CR_TKT) {
+           printf ("NO\n");
+       } else if (r == 0) {
+           printf ("YES\n");
+       } else {
+           printf ("kablooey!\n");
+           com_err (me, r, "checking transited-realm list");
+           return 1;
+       }
+       return 0;
+    }
 }
+
+#endif /* TEST */
index 8d7a4d9759f925635cfa91a4acdbdb50e96c01f5..b25b1d38aba5607185d58c7282428617107d2216 100644 (file)
                v4_realm = SOME-REALLY-LONG-REALM-NAME-V4-CANNOT-HANDLE.COM
        }
 
+[capaths]
+       /COM/HP/APOLLO/FOO = {
+               /COM/DEC/CRL = /COM/DEC
+               /COM/DEC/CRL = /COM
+               /COM/DEC/CRL = /COM/HP
+               /COM/DEC/CRL = /COM/HP/APOLLO
+       }
+       ATHENA.MIT.EDU = {
+               KERBEROS.COM = .
+       }
+       LCS.MIT.EDU = {
+               KERBEROS.COM = ATHENA.MIT.EDU
+               ATHENA.MIT.EDU = .
+               KABLOOEY.KERBEROS.COM = ATHENA.MIT.EDU
+               KABLOOEY.KERBEROS.COM = KERBEROS.COM
+       }
+
 [domain_realm]
        .mit.edu = ATHENA.MIT.EDU
        mit.edu = ATHENA.MIT.EDU
diff --git a/src/lib/krb5/krb/transit-tests b/src/lib/krb5/krb/transit-tests
new file mode 100644 (file)
index 0000000..8ffb9c3
--- /dev/null
@@ -0,0 +1,54 @@
+#!/bin/sh
+
+# Test the chk_trans.c code.
+# BUG: Currently only tests expansion, not validation.
+
+trap "echo Failed. ; exit 1" 0
+
+check='echo Running test "($1) ($2) ($3)" ... ; ./t_expand -x >tmpout1 "$@" || exit 1 ; grep -v : <tmpout1 >tmpout2 && sort <tmpout2 >tmpout1 && echo Got: `cat tmpout1` && (for i in $expected ; do echo $i ; done) | sort > tmpout2 && echo Exp: `cat tmpout2` && echo "" && cmp >/dev/null 2>&1 tmpout1 tmpout2 || exit 1'
+checkerror='echo Running test "($1) ($2) ($3)", expecting error ... ; if ./t_expand -x >tmpout1 "$@" ; then echo Error was expected, but not reported. ; exit 1; else echo Expected error found. ; echo ""; fi'
+
+#
+# Note: Expected realm expansion order is not important; program output
+# and expected values will each be sorted before comparison.
+#
+
+set ATHENA.MIT.EDU HACK.FOOBAR.COM ,EDU,BLORT.COM,COM,
+expected="MIT.EDU EDU BLORT.COM COM FOOBAR.COM"
+eval $check
+
+set ATHENA.MIT.EDU EDU ,
+expected="MIT.EDU"
+eval $check
+
+set EDU ATHENA.MIT.EDU ,
+expected="MIT.EDU"
+eval $check
+
+set x x "/COM,/HP,/APOLLO, /COM/DEC"
+expected="/COM /COM/HP /COM/HP/APOLLO /COM/DEC"
+eval $check
+
+set x x EDU,MIT.,ATHENA.,WASHINGTON.EDU,CS.
+expected="EDU MIT.EDU ATHENA.MIT.EDU WASHINGTON.EDU CS.WASHINGTON.EDU"
+eval $check
+
+set ATHENA.MIT.EDU /COM/HP/APOLLO ,EDU,/COM,
+eval $checkerror
+
+set ATHENA.MIT.EDU /COM/HP/APOLLO ",EDU, /COM,"
+expected="EDU MIT.EDU /COM /COM/HP"
+eval $check
+
+set ATHENA.MIT.EDU CS.CMU.EDU ,EDU,
+expected="EDU MIT.EDU CMU.EDU"
+eval $check
+
+set XYZZY.ATHENA.MIT.EDU XYZZY.CS.CMU.EDU ,EDU,
+expected="EDU MIT.EDU ATHENA.MIT.EDU CMU.EDU CS.CMU.EDU"
+eval $check
+
+rm tmpout1 tmpout2
+trap "" 0
+echo Success.
+exit 0