aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKen Raeburn <raeburn@mit.edu>2001-06-20 04:07:43 +0000
committerKen Raeburn <raeburn@mit.edu>2001-06-20 04:07:43 +0000
commit308665f3840b56b47e347dad9ab9400aa123e089 (patch)
treefabe19188b3eb2b93ae36df89097b866fc2ceda5
parent98aa032b6b9a638db75486439f285dc77bf0f91d (diff)
downloadkrb5-308665f3840b56b47e347dad9ab9400aa123e089.zip
krb5-308665f3840b56b47e347dad9ab9400aa123e089.tar.gz
krb5-308665f3840b56b47e347dad9ab9400aa123e089.tar.bz2
New implementation of transited-realm checking, with some test cases. The test
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
-rw-r--r--src/lib/krb5/krb/ChangeLog11
-rw-r--r--src/lib/krb5/krb/Makefile.in9
-rw-r--r--src/lib/krb5/krb/chk_trans.c482
-rw-r--r--src/lib/krb5/krb/t_krb5.conf17
-rw-r--r--src/lib/krb5/krb/transit-tests54
5 files changed, 494 insertions, 79 deletions
diff --git a/src/lib/krb5/krb/ChangeLog b/src/lib/krb5/krb/ChangeLog
index 8f38c32..4c86bb1 100644
--- a/src/lib/krb5/krb/ChangeLog
+++ b/src/lib/krb5/krb/ChangeLog
@@ -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
diff --git a/src/lib/krb5/krb/Makefile.in b/src/lib/krb5/krb/Makefile.in
index 6418178..c07bd47 100644
--- a/src/lib/krb5/krb/Makefile.in
+++ b/src/lib/krb5/krb/Makefile.in
@@ -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) \
diff --git a/src/lib/krb5/krb/chk_trans.c b/src/lib/krb5/krb/chk_trans.c
index 357c438..87a32c2 100644
--- a/src/lib/krb5/krb/chk_trans.c
+++ b/src/lib/krb5/krb/chk_trans.c
@@ -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
@@ -14,98 +16,422 @@
* 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;
+}
+
+
+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;
+}
+
+#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 */
diff --git a/src/lib/krb5/krb/t_krb5.conf b/src/lib/krb5/krb/t_krb5.conf
index 8d7a4d9..b25b1d3 100644
--- a/src/lib/krb5/krb/t_krb5.conf
+++ b/src/lib/krb5/krb/t_krb5.conf
@@ -26,6 +26,23 @@
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
index 0000000..8ffb9c3
--- /dev/null
+++ b/src/lib/krb5/krb/transit-tests
@@ -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