Initial support for adding comments that will be emitted when

sudoers is formatted.  Currently adds a comment for the source
sudoRole when converting from ldif -> sudoers.
This commit is contained in:
Todd C. Miller
2018-03-04 07:03:43 -07:00
parent 670d8e6d77
commit 5c36f9dec3
13 changed files with 224 additions and 73 deletions

View File

@@ -461,7 +461,7 @@ check: $(TEST_PROGS) visudo testsudoers
diff $$json $(srcdir)/$$json.ok || true; \ diff $$json $(srcdir)/$$json.ok || true; \
fi; \ fi; \
SUDOERS_BASE="ou=SUDOers,dc=sudo,dc=ws" \ SUDOERS_BASE="ou=SUDOers,dc=sudo,dc=ws" \
./cvtsudoers -c "" -f ldif $$t >$$ldif 2>/dev/null || true; \ ./cvtsudoers -c "" -f ldif < $$t >$$ldif 2>/dev/null || true; \
total=`expr $$total + 1`; \ total=`expr $$total + 1`; \
if cmp $$ldif $(srcdir)/$$ldif.ok >/dev/null; then \ if cmp $$ldif $(srcdir)/$$ldif.ok >/dev/null; then \
passed=`expr $$passed + 1`; \ passed=`expr $$passed + 1`; \

View File

@@ -455,51 +455,9 @@ print_defaults_sudoers(struct sudo_lbuf *lbuf, bool expand_aliases)
struct defaults *def, *next; struct defaults *def, *next;
debug_decl(print_defaults_sudoers, SUDOERS_DEBUG_UTIL) debug_decl(print_defaults_sudoers, SUDOERS_DEBUG_UTIL)
TAILQ_FOREACH_SAFE(def, &defaults, entries, next) { TAILQ_FOREACH_SAFE(def, &defaults, entries, next)
struct member *m; sudoers_format_default_line(lbuf, def, &next, expand_aliases);
int alias_type;
/* Print Defaults type and binding (if present) */
switch (def->type) {
case DEFAULTS_HOST:
sudo_lbuf_append(lbuf, "Defaults@");
alias_type = HOSTALIAS;
break;
case DEFAULTS_USER:
sudo_lbuf_append(lbuf, "Defaults:");
alias_type = expand_aliases ? USERALIAS : UNSPEC;
break;
case DEFAULTS_RUNAS:
sudo_lbuf_append(lbuf, "Defaults>");
alias_type = expand_aliases ? RUNASALIAS : UNSPEC;
break;
case DEFAULTS_CMND:
sudo_lbuf_append(lbuf, "Defaults!");
alias_type = expand_aliases ? CMNDALIAS : UNSPEC;
break;
default:
sudo_lbuf_append(lbuf, "Defaults");
alias_type = UNSPEC;
break;
}
TAILQ_FOREACH(m, def->binding, entries) {
if (m != TAILQ_FIRST(def->binding))
sudo_lbuf_append(lbuf, ", ");
sudoers_format_member(lbuf, m, ", ", alias_type);
}
/* Print Defaults with the same binding, there may be multiple. */
for (;;) {
sudo_lbuf_append(lbuf, " ");
sudoers_format_default(lbuf, def);
next = TAILQ_NEXT(def, entries);
if (next == NULL || def->binding != next->binding)
break;
def = next;
sudo_lbuf_append(lbuf, ",");
}
sudo_lbuf_append(lbuf, "\n");
}
debug_return_bool(!sudo_lbuf_error(lbuf)); debug_return_bool(!sudo_lbuf_error(lbuf));
} }

View File

@@ -34,6 +34,7 @@
#include "parse.h" #include "parse.h"
#include "redblack.h" #include "redblack.h"
#include "cvtsudoers.h" #include "cvtsudoers.h"
#include "sudo_lbuf.h"
#include <gram.h> #include <gram.h>
struct seen_user { struct seen_user {
@@ -95,14 +96,26 @@ static bool
print_global_defaults_ldif(FILE *fp, const char *base) print_global_defaults_ldif(FILE *fp, const char *base)
{ {
unsigned int count = 0; unsigned int count = 0;
struct sudo_lbuf lbuf;
struct defaults *opt; struct defaults *opt;
debug_decl(print_global_defaults_ldif, SUDOERS_DEBUG_UTIL) debug_decl(print_global_defaults_ldif, SUDOERS_DEBUG_UTIL)
sudo_lbuf_init(&lbuf, NULL, 0, NULL, 80);
TAILQ_FOREACH(opt, &defaults, entries) { TAILQ_FOREACH(opt, &defaults, entries) {
/* Skip bound Defaults (unsupported). */ /* Skip bound Defaults (unsupported). */
if (opt->type == DEFAULTS) if (opt->type == DEFAULTS) {
count++; count++;
} else {
lbuf.len = 0;
sudo_lbuf_append(&lbuf, "# ");
sudoers_format_default_line(&lbuf, opt, false, true);
fprintf(fp, "# Unable to translate %s:%d\n%s\n",
opt->file, opt->lineno, lbuf.buf);
}
} }
sudo_lbuf_destroy(&lbuf);
if (count == 0) if (count == 0)
debug_return_bool(true); debug_return_bool(true);
@@ -118,24 +131,6 @@ print_global_defaults_ldif(FILE *fp, const char *base)
debug_return_bool(!ferror(fp)); debug_return_bool(!ferror(fp));
} }
static void
warn_bound_defaults_ldif(FILE *fp)
{
struct defaults *def;
debug_decl(warn_bound_defaults_ldif, SUDOERS_DEBUG_UTIL)
TAILQ_FOREACH(def, &defaults, entries) {
if (def->type == DEFAULTS)
continue; /* only want bound defaults */
/* XXX - print Defaults line */
sudo_warnx(U_("%s:%d unable to translate Defaults line"),
def->file, def->lineno);
}
debug_return;
}
/* /*
* Print struct member in LDIF format, with specified prefix. * Print struct member in LDIF format, with specified prefix.
* See print_member_int() in parse.c. * See print_member_int() in parse.c.
@@ -506,9 +501,6 @@ convert_sudoers_ldif(const char *output_file, struct cvtsudoers_config *conf)
/* Dump User_Specs in LDIF format, expanding Aliases. */ /* Dump User_Specs in LDIF format, expanding Aliases. */
print_userspecs_ldif(output_fp, conf); print_userspecs_ldif(output_fp, conf);
/* Warn about non-translatable Defaults entries. */
warn_bound_defaults_ldif(output_fp);
/* Clean up. */ /* Clean up. */
rbdestroy(seen_users, seen_user_free); rbdestroy(seen_users, seen_user_free);
@@ -812,7 +804,6 @@ role_to_sudoers(struct sudo_role *role, bool store_options,
/* /*
* TODO: use cn to create a UserAlias if multiple users in it? * TODO: use cn to create a UserAlias if multiple users in it?
* TODO: add comment info based on cn?
*/ */
if (reuse_userspec) { if (reuse_userspec) {
@@ -826,6 +817,7 @@ role_to_sudoers(struct sudo_role *role, bool store_options,
} }
TAILQ_INIT(&us->privileges); TAILQ_INIT(&us->privileges);
TAILQ_INIT(&us->users); TAILQ_INIT(&us->users);
STAILQ_INIT(&us->comments);
STAILQ_FOREACH(ls, role->users, entries) { STAILQ_FOREACH(ls, role->users, entries) {
char *user = ls->str; char *user = ls->str;
@@ -853,6 +845,38 @@ role_to_sudoers(struct sudo_role *role, bool store_options,
} }
} }
/* Add source role as a comment. */
if (role->cn != NULL) {
struct comment *comment = NULL;
if (reuse_userspec) {
/* Try to re-use comment too. */
STAILQ_FOREACH(comment, &us->comments, entries) {
if (strncmp(comment->str, "sudoRole ", 9) == 0) {
char *tmpstr;
if (asprintf(&tmpstr, "%s, %s", comment->str, role->cn) == -1) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
free(comment->str);
comment->str = tmpstr;
break;
}
}
}
if (comment == NULL) {
/* Create a new comment. */
if ((comment = malloc(sizeof(*comment))) == NULL) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
if (asprintf(&comment->str, "sudoRole %s", role->cn) == -1) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
STAILQ_INSERT_TAIL(&us->comments, comment, entries);
}
}
/* Convert role to sudoers privilege. */ /* Convert role to sudoers privilege. */
priv = sudo_ldap_role_to_priv(role->cn, STAILQ_FIRST(role->hosts), priv = sudo_ldap_role_to_priv(role->cn, STAILQ_FIRST(role->hosts),
STAILQ_FIRST(role->runasusers), STAILQ_FIRST(role->runasgroups), STAILQ_FIRST(role->runasusers), STAILQ_FIRST(role->runasgroups),
@@ -966,6 +990,32 @@ ldif_to_sudoers(struct sudo_role_list *roles, unsigned int numroles,
debug_return; debug_return;
} }
/*
* Given a cn with possible quoted characters, return a copy of
* the cn with quote characters ('\\') removed.
* The caller is responsible for freeing the returned string.
*/
static
char *unquote_cn(const char *src)
{
char *dst, *new_cn;
size_t len;
debug_decl(unquote_cn, SUDOERS_DEBUG_UTIL)
len = strlen(src);
if ((new_cn = malloc(len + 1)) == NULL)
debug_return_str(NULL);
for (dst = new_cn; *src != '\0';) {
if (src[0] == '\\' && src[1] != '\0')
src++;
*dst++ = *src++;
}
*dst = '\0';
debug_return_str(new_cn);
}
/* /*
* Parse a sudoers file in LDIF format, https://tools.ietf.org/html/rfc2849 * Parse a sudoers file in LDIF format, https://tools.ietf.org/html/rfc2849
* Parsed sudoRole objects are stored in the global sudoers data structures. * Parsed sudoRole objects are stored in the global sudoers data structures.
@@ -1136,8 +1186,7 @@ parse_ldif(const char *input_file, struct cvtsudoers_config *conf)
while (isblank((unsigned char)*cp)) while (isblank((unsigned char)*cp))
cp++; cp++;
free(role->cn); free(role->cn);
/* XXX - unescape chars? */ role->cn = unquote_cn(cp);
role->cn = strdup(cp);
if (role->cn == NULL) { if (role->cn == NULL) {
sudo_fatalx(U_("%s: %s"), __func__, sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory")); U_("unable to allocate memory"));

View File

@@ -256,9 +256,15 @@ sudoers_format_userspec(struct sudo_lbuf *lbuf, struct userspec *us,
bool expand_aliases) bool expand_aliases)
{ {
struct privilege *priv; struct privilege *priv;
struct comment *comment;
struct member *m; struct member *m;
debug_decl(sudoers_format_userspec, SUDOERS_DEBUG_UTIL) debug_decl(sudoers_format_userspec, SUDOERS_DEBUG_UTIL)
/* Print comments (if any). */
STAILQ_FOREACH(comment, &us->comments, entries) {
sudo_lbuf_append(lbuf, "# %s\n", comment->str);
}
/* Print users list. */ /* Print users list. */
TAILQ_FOREACH(m, &us->users, entries) { TAILQ_FOREACH(m, &us->users, entries) {
if (m != TAILQ_FIRST(&us->users)) if (m != TAILQ_FIRST(&us->users))
@@ -291,9 +297,10 @@ sudoers_format_userspecs(struct sudo_lbuf *lbuf, struct userspec_list *usl,
debug_decl(sudoers_format_userspecs, SUDOERS_DEBUG_UTIL) debug_decl(sudoers_format_userspecs, SUDOERS_DEBUG_UTIL)
TAILQ_FOREACH(us, usl, entries) { TAILQ_FOREACH(us, usl, entries) {
if (us != TAILQ_FIRST(usl))
sudo_lbuf_append(lbuf, sep);
if (!sudoers_format_userspec(lbuf, us, expand_aliases)) if (!sudoers_format_userspec(lbuf, us, expand_aliases))
break; break;
sudo_lbuf_append(lbuf, sep);
sudo_lbuf_print(lbuf); sudo_lbuf_print(lbuf);
} }
@@ -322,3 +329,64 @@ sudoers_format_default(struct sudo_lbuf *lbuf, struct defaults *d)
} }
debug_return_bool(!sudo_lbuf_error(lbuf)); debug_return_bool(!sudo_lbuf_error(lbuf));
} }
/*
* Format and append a defaults line to the specified lbuf.
* If next, is specified, it must point to the next defaults
* entry in the list; this is used to print multiple defaults
* entries with the same binding on a single line.
*/
bool
sudoers_format_default_line(struct sudo_lbuf *lbuf, struct defaults *d,
struct defaults **next, bool expand_aliases)
{
struct member *m;
int alias_type;
debug_decl(sudoers_format_default_line, SUDOERS_DEBUG_UTIL)
/* Print Defaults type and binding (if present) */
switch (d->type) {
case DEFAULTS_HOST:
sudo_lbuf_append(lbuf, "Defaults@");
alias_type = HOSTALIAS;
break;
case DEFAULTS_USER:
sudo_lbuf_append(lbuf, "Defaults:");
alias_type = expand_aliases ? USERALIAS : UNSPEC;
break;
case DEFAULTS_RUNAS:
sudo_lbuf_append(lbuf, "Defaults>");
alias_type = expand_aliases ? RUNASALIAS : UNSPEC;
break;
case DEFAULTS_CMND:
sudo_lbuf_append(lbuf, "Defaults!");
alias_type = expand_aliases ? CMNDALIAS : UNSPEC;
break;
default:
sudo_lbuf_append(lbuf, "Defaults");
alias_type = UNSPEC;
break;
}
TAILQ_FOREACH(m, d->binding, entries) {
if (m != TAILQ_FIRST(d->binding))
sudo_lbuf_append(lbuf, ", ");
sudoers_format_member(lbuf, m, ", ", alias_type);
}
sudo_lbuf_append(lbuf, " ");
sudoers_format_default(lbuf, d);
if (next != NULL) {
/* Merge Defaults with the same binding, there may be multiple. */
struct defaults *n = *next;
while ((n = TAILQ_NEXT(d, entries)) && d->binding == n->binding) {
sudo_lbuf_append(lbuf, ", ");
sudoers_format_default(lbuf, n);
d = n;
}
*next = n;
}
sudo_lbuf_append(lbuf, "\n");
debug_return_bool(!sudo_lbuf_error(lbuf));
}

View File

@@ -825,6 +825,7 @@ add_userspec(struct member *members, struct privilege *privs)
u->file = rcstr_addref(sudoers); u->file = rcstr_addref(sudoers);
HLTQ_TO_TAILQ(&u->users, members, entries); HLTQ_TO_TAILQ(&u->users, members, entries);
HLTQ_TO_TAILQ(&u->privileges, privs, entries); HLTQ_TO_TAILQ(&u->privileges, privs, entries);
STAILQ_INIT(&u->comments);
TAILQ_INSERT_TAIL(&userspecs, u, entries); TAILQ_INSERT_TAIL(&userspecs, u, entries);
debug_return_bool(true); debug_return_bool(true);
@@ -929,12 +930,18 @@ void
free_userspec(struct userspec *us) free_userspec(struct userspec *us)
{ {
struct privilege *priv; struct privilege *priv;
struct comment *comment;
free_members(&us->users); free_members(&us->users);
while ((priv = TAILQ_FIRST(&us->privileges)) != NULL) { while ((priv = TAILQ_FIRST(&us->privileges)) != NULL) {
TAILQ_REMOVE(&us->privileges, priv, entries); TAILQ_REMOVE(&us->privileges, priv, entries);
free_privilege(priv); free_privilege(priv);
} }
while ((comment = STAILQ_FIRST(&us->comments)) != NULL) {
STAILQ_REMOVE_HEAD(&us->comments, entries);
free(comment->str);
free(comment);
}
rcstr_delref(us->file); rcstr_delref(us->file);
free(us); free(us);
} }
@@ -1016,7 +1023,7 @@ init_options(struct command_options *opts)
opts->limitprivs = NULL; opts->limitprivs = NULL;
#endif #endif
} }
#line 967 "gram.c" #line 974 "gram.c"
/* allocate initial stack or double stack size, up to YYMAXDEPTH */ /* allocate initial stack or double stack size, up to YYMAXDEPTH */
#if defined(__cplusplus) || defined(__STDC__) #if defined(__cplusplus) || defined(__STDC__)
static int yygrowstack(void) static int yygrowstack(void)
@@ -2141,7 +2148,7 @@ case 116:
} }
} }
break; break;
#line 2092 "gram.c" #line 2099 "gram.c"
} }
yyssp -= yym; yyssp -= yym;
yystate = *yyssp; yystate = *yyssp;

View File

@@ -1053,6 +1053,7 @@ add_userspec(struct member *members, struct privilege *privs)
u->file = rcstr_addref(sudoers); u->file = rcstr_addref(sudoers);
HLTQ_TO_TAILQ(&u->users, members, entries); HLTQ_TO_TAILQ(&u->users, members, entries);
HLTQ_TO_TAILQ(&u->privileges, privs, entries); HLTQ_TO_TAILQ(&u->privileges, privs, entries);
STAILQ_INIT(&u->comments);
TAILQ_INSERT_TAIL(&userspecs, u, entries); TAILQ_INSERT_TAIL(&userspecs, u, entries);
debug_return_bool(true); debug_return_bool(true);
@@ -1157,12 +1158,18 @@ void
free_userspec(struct userspec *us) free_userspec(struct userspec *us)
{ {
struct privilege *priv; struct privilege *priv;
struct comment *comment;
free_members(&us->users); free_members(&us->users);
while ((priv = TAILQ_FIRST(&us->privileges)) != NULL) { while ((priv = TAILQ_FIRST(&us->privileges)) != NULL) {
TAILQ_REMOVE(&us->privileges, priv, entries); TAILQ_REMOVE(&us->privileges, priv, entries);
free_privilege(priv); free_privilege(priv);
} }
while ((comment = STAILQ_FIRST(&us->comments)) != NULL) {
STAILQ_REMOVE_HEAD(&us->comments, entries);
free(comment->str);
free(comment);
}
rcstr_delref(us->file); rcstr_delref(us->file);
free(us); free(us);
} }

View File

@@ -1519,6 +1519,7 @@ ldap_to_sudoers(LDAP *ld, struct ldap_result *lres)
goto oom; goto oom;
TAILQ_INIT(&us->users); TAILQ_INIT(&us->users);
TAILQ_INIT(&us->privileges); TAILQ_INIT(&us->privileges);
STAILQ_INIT(&us->comments);
TAILQ_INSERT_TAIL(ldap_userspecs, us, entries); TAILQ_INSERT_TAIL(ldap_userspecs, us, entries);
/* The user has already matched, use ALL as wildcard. */ /* The user has already matched, use ALL as wildcard. */

View File

@@ -461,6 +461,7 @@ sudo_ldap_role_to_priv(const char *cn, void *hosts, void *runasusers,
handled = false; handled = false;
} }
if (!handled && warnings) { if (!handled && warnings) {
/* XXX - callback to process unsupported options. */
if (val != NULL) { if (val != NULL) {
sudo_warnx(U_("unable to convert sudoOption: %s%s%s"), var, op == '+' ? "+=" : op == '-' ? "-=" : "=", val); sudo_warnx(U_("unable to convert sudoOption: %s%s%s"), var, op == '+' ? "+=" : op == '-' ? "-=" : "=", val);
} else { } else {

View File

@@ -150,6 +150,7 @@ TAILQ_HEAD(userspec_list, userspec);
TAILQ_HEAD(member_list, member); TAILQ_HEAD(member_list, member);
TAILQ_HEAD(privilege_list, privilege); TAILQ_HEAD(privilege_list, privilege);
TAILQ_HEAD(cmndspec_list, cmndspec); TAILQ_HEAD(cmndspec_list, cmndspec);
STAILQ_HEAD(comment_list, comment);
/* /*
* Structure describing a user specification and list thereof. * Structure describing a user specification and list thereof.
@@ -158,6 +159,7 @@ struct userspec {
TAILQ_ENTRY(userspec) entries; TAILQ_ENTRY(userspec) entries;
struct member_list users; /* list of users */ struct member_list users; /* list of users */
struct privilege_list privileges; /* list of privileges */ struct privilege_list privileges; /* list of privileges */
struct comment_list comments; /* optional comments */
int lineno; int lineno;
char *file; char *file;
}; };
@@ -209,6 +211,11 @@ struct runascontainer {
struct member *runasgroups; struct member *runasgroups;
}; };
struct comment {
STAILQ_ENTRY(comment) entries;
char *str;
};
/* /*
* Generic structure to hold {User,Host,Runas,Cmnd}_Alias * Generic structure to hold {User,Host,Runas,Cmnd}_Alias
* Aliases are stored in a red-black tree, sorted by name and type. * Aliases are stored in a red-black tree, sorted by name and type.
@@ -312,6 +319,7 @@ int sudo_display_userspecs(struct userspec_list *usl, struct passwd *pw, struct
/* fmtsudoers.c */ /* fmtsudoers.c */
bool sudoers_format_cmndspec(struct sudo_lbuf *lbuf, struct cmndspec *cs, struct cmndspec *prev_cs, bool expand_aliases); bool sudoers_format_cmndspec(struct sudo_lbuf *lbuf, struct cmndspec *cs, struct cmndspec *prev_cs, bool expand_aliases);
bool sudoers_format_default(struct sudo_lbuf *lbuf, struct defaults *d); bool sudoers_format_default(struct sudo_lbuf *lbuf, struct defaults *d);
bool sudoers_format_default_line(struct sudo_lbuf *lbuf, struct defaults *d, struct defaults **next, bool expand_aliases);
bool sudoers_format_member(struct sudo_lbuf *lbuf, struct member *m, const char *separator, int alias_type); bool sudoers_format_member(struct sudo_lbuf *lbuf, struct member *m, const char *separator, int alias_type);
bool sudoers_format_privilege(struct sudo_lbuf *lbuf, struct privilege *priv, bool expand_aliases); bool sudoers_format_privilege(struct sudo_lbuf *lbuf, struct privilege *priv, bool expand_aliases);
bool sudoers_format_userspec(struct sudo_lbuf *lbuf, struct userspec *us, bool expand_aliases); bool sudoers_format_userspec(struct sudo_lbuf *lbuf, struct userspec *us, bool expand_aliases);

View File

@@ -1,3 +1,30 @@
# Unable to translate stdin:26
# Defaults@somehost set_home
# Unable to translate stdin:27
# Defaults@quoted\" set_home
# Unable to translate stdin:30
# Defaults:you set_home
# Unable to translate stdin:31
# Defaults:us\" set_home
# Unable to translate stdin:32
# Defaults:%them set_home
# Unable to translate stdin:33
# Defaults:"%: non UNIX 0 c" set_home
# Unable to translate stdin:34
# Defaults:+net set_home
# Unable to translate stdin:37
# Defaults>someone set_home
# Unable to translate stdin:38
# Defaults>"some one" set_home
dn: cn=foo,ou=SUDOers,dc=sudo,dc=ws dn: cn=foo,ou=SUDOers,dc=sudo,dc=ws
objectClass: top objectClass: top
objectClass: sudoRole objectClass: sudoRole

View File

@@ -0,0 +1,12 @@
# Unable to translate stdin:3
# Defaults:foo, bar env_reset
# Unable to translate stdin:4
# Defaults:foo, bar env_reset
# Unable to translate stdin:5
# Defaults:foo, " bar" env_reset
# Unable to translate stdin:6
# Defaults:foo, bar env_reset

View File

@@ -1,3 +1,15 @@
# Unable to translate stdin:2
# Defaults:#123 set_home
# Unable to translate stdin:3
# Defaults>#123 set_home
# Unable to translate stdin:4
# Defaults:#123 set_home
# Unable to translate stdin:5
# Defaults>#123 set_home
dn: cn=\#0,ou=SUDOers,dc=sudo,dc=ws dn: cn=\#0,ou=SUDOers,dc=sudo,dc=ws
objectClass: top objectClass: top
objectClass: sudoRole objectClass: sudoRole

View File

@@ -1436,6 +1436,7 @@ sss_to_sudoers(struct sudo_sss_handle *handle, struct sss_sudo_result *sss_resul
goto oom; goto oom;
TAILQ_INIT(&us->users); TAILQ_INIT(&us->users);
TAILQ_INIT(&us->privileges); TAILQ_INIT(&us->privileges);
STAILQ_INIT(&us->comments);
TAILQ_INSERT_TAIL(sss_userspecs, us, entries); TAILQ_INSERT_TAIL(sss_userspecs, us, entries);
/* The user has already matched, use ALL as wildcard. */ /* The user has already matched, use ALL as wildcard. */