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:
@@ -461,7 +461,7 @@ check: $(TEST_PROGS) visudo testsudoers
|
||||
diff $$json $(srcdir)/$$json.ok || true; \
|
||||
fi; \
|
||||
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`; \
|
||||
if cmp $$ldif $(srcdir)/$$ldif.ok >/dev/null; then \
|
||||
passed=`expr $$passed + 1`; \
|
||||
|
@@ -455,51 +455,9 @@ print_defaults_sudoers(struct sudo_lbuf *lbuf, bool expand_aliases)
|
||||
struct defaults *def, *next;
|
||||
debug_decl(print_defaults_sudoers, SUDOERS_DEBUG_UTIL)
|
||||
|
||||
TAILQ_FOREACH_SAFE(def, &defaults, entries, next) {
|
||||
struct member *m;
|
||||
int alias_type;
|
||||
TAILQ_FOREACH_SAFE(def, &defaults, entries, next)
|
||||
sudoers_format_default_line(lbuf, def, &next, expand_aliases);
|
||||
|
||||
/* 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));
|
||||
}
|
||||
|
||||
|
@@ -34,6 +34,7 @@
|
||||
#include "parse.h"
|
||||
#include "redblack.h"
|
||||
#include "cvtsudoers.h"
|
||||
#include "sudo_lbuf.h"
|
||||
#include <gram.h>
|
||||
|
||||
struct seen_user {
|
||||
@@ -95,14 +96,26 @@ static bool
|
||||
print_global_defaults_ldif(FILE *fp, const char *base)
|
||||
{
|
||||
unsigned int count = 0;
|
||||
struct sudo_lbuf lbuf;
|
||||
struct defaults *opt;
|
||||
debug_decl(print_global_defaults_ldif, SUDOERS_DEBUG_UTIL)
|
||||
|
||||
sudo_lbuf_init(&lbuf, NULL, 0, NULL, 80);
|
||||
|
||||
TAILQ_FOREACH(opt, &defaults, entries) {
|
||||
/* Skip bound Defaults (unsupported). */
|
||||
if (opt->type == DEFAULTS)
|
||||
if (opt->type == DEFAULTS) {
|
||||
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)
|
||||
debug_return_bool(true);
|
||||
|
||||
@@ -118,24 +131,6 @@ print_global_defaults_ldif(FILE *fp, const char *base)
|
||||
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.
|
||||
* 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. */
|
||||
print_userspecs_ldif(output_fp, conf);
|
||||
|
||||
/* Warn about non-translatable Defaults entries. */
|
||||
warn_bound_defaults_ldif(output_fp);
|
||||
|
||||
/* Clean up. */
|
||||
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: add comment info based on cn?
|
||||
*/
|
||||
|
||||
if (reuse_userspec) {
|
||||
@@ -826,6 +817,7 @@ role_to_sudoers(struct sudo_role *role, bool store_options,
|
||||
}
|
||||
TAILQ_INIT(&us->privileges);
|
||||
TAILQ_INIT(&us->users);
|
||||
STAILQ_INIT(&us->comments);
|
||||
|
||||
STAILQ_FOREACH(ls, role->users, entries) {
|
||||
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. */
|
||||
priv = sudo_ldap_role_to_priv(role->cn, STAILQ_FIRST(role->hosts),
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
|
||||
* 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))
|
||||
cp++;
|
||||
free(role->cn);
|
||||
/* XXX - unescape chars? */
|
||||
role->cn = strdup(cp);
|
||||
role->cn = unquote_cn(cp);
|
||||
if (role->cn == NULL) {
|
||||
sudo_fatalx(U_("%s: %s"), __func__,
|
||||
U_("unable to allocate memory"));
|
||||
|
@@ -256,9 +256,15 @@ sudoers_format_userspec(struct sudo_lbuf *lbuf, struct userspec *us,
|
||||
bool expand_aliases)
|
||||
{
|
||||
struct privilege *priv;
|
||||
struct comment *comment;
|
||||
struct member *m;
|
||||
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. */
|
||||
TAILQ_FOREACH(m, &us->users, entries) {
|
||||
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)
|
||||
|
||||
TAILQ_FOREACH(us, usl, entries) {
|
||||
if (us != TAILQ_FIRST(usl))
|
||||
sudo_lbuf_append(lbuf, sep);
|
||||
if (!sudoers_format_userspec(lbuf, us, expand_aliases))
|
||||
break;
|
||||
sudo_lbuf_append(lbuf, sep);
|
||||
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));
|
||||
}
|
||||
|
||||
/*
|
||||
* 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));
|
||||
}
|
||||
|
@@ -825,6 +825,7 @@ add_userspec(struct member *members, struct privilege *privs)
|
||||
u->file = rcstr_addref(sudoers);
|
||||
HLTQ_TO_TAILQ(&u->users, members, entries);
|
||||
HLTQ_TO_TAILQ(&u->privileges, privs, entries);
|
||||
STAILQ_INIT(&u->comments);
|
||||
TAILQ_INSERT_TAIL(&userspecs, u, entries);
|
||||
|
||||
debug_return_bool(true);
|
||||
@@ -929,12 +930,18 @@ void
|
||||
free_userspec(struct userspec *us)
|
||||
{
|
||||
struct privilege *priv;
|
||||
struct comment *comment;
|
||||
|
||||
free_members(&us->users);
|
||||
while ((priv = TAILQ_FIRST(&us->privileges)) != NULL) {
|
||||
TAILQ_REMOVE(&us->privileges, priv, entries);
|
||||
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);
|
||||
free(us);
|
||||
}
|
||||
@@ -1016,7 +1023,7 @@ init_options(struct command_options *opts)
|
||||
opts->limitprivs = NULL;
|
||||
#endif
|
||||
}
|
||||
#line 967 "gram.c"
|
||||
#line 974 "gram.c"
|
||||
/* allocate initial stack or double stack size, up to YYMAXDEPTH */
|
||||
#if defined(__cplusplus) || defined(__STDC__)
|
||||
static int yygrowstack(void)
|
||||
@@ -2141,7 +2148,7 @@ case 116:
|
||||
}
|
||||
}
|
||||
break;
|
||||
#line 2092 "gram.c"
|
||||
#line 2099 "gram.c"
|
||||
}
|
||||
yyssp -= yym;
|
||||
yystate = *yyssp;
|
||||
|
@@ -1053,6 +1053,7 @@ add_userspec(struct member *members, struct privilege *privs)
|
||||
u->file = rcstr_addref(sudoers);
|
||||
HLTQ_TO_TAILQ(&u->users, members, entries);
|
||||
HLTQ_TO_TAILQ(&u->privileges, privs, entries);
|
||||
STAILQ_INIT(&u->comments);
|
||||
TAILQ_INSERT_TAIL(&userspecs, u, entries);
|
||||
|
||||
debug_return_bool(true);
|
||||
@@ -1157,12 +1158,18 @@ void
|
||||
free_userspec(struct userspec *us)
|
||||
{
|
||||
struct privilege *priv;
|
||||
struct comment *comment;
|
||||
|
||||
free_members(&us->users);
|
||||
while ((priv = TAILQ_FIRST(&us->privileges)) != NULL) {
|
||||
TAILQ_REMOVE(&us->privileges, priv, entries);
|
||||
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);
|
||||
free(us);
|
||||
}
|
||||
|
@@ -1519,6 +1519,7 @@ ldap_to_sudoers(LDAP *ld, struct ldap_result *lres)
|
||||
goto oom;
|
||||
TAILQ_INIT(&us->users);
|
||||
TAILQ_INIT(&us->privileges);
|
||||
STAILQ_INIT(&us->comments);
|
||||
TAILQ_INSERT_TAIL(ldap_userspecs, us, entries);
|
||||
|
||||
/* The user has already matched, use ALL as wildcard. */
|
||||
|
@@ -461,6 +461,7 @@ sudo_ldap_role_to_priv(const char *cn, void *hosts, void *runasusers,
|
||||
handled = false;
|
||||
}
|
||||
if (!handled && warnings) {
|
||||
/* XXX - callback to process unsupported options. */
|
||||
if (val != NULL) {
|
||||
sudo_warnx(U_("unable to convert sudoOption: %s%s%s"), var, op == '+' ? "+=" : op == '-' ? "-=" : "=", val);
|
||||
} else {
|
||||
|
@@ -150,6 +150,7 @@ TAILQ_HEAD(userspec_list, userspec);
|
||||
TAILQ_HEAD(member_list, member);
|
||||
TAILQ_HEAD(privilege_list, privilege);
|
||||
TAILQ_HEAD(cmndspec_list, cmndspec);
|
||||
STAILQ_HEAD(comment_list, comment);
|
||||
|
||||
/*
|
||||
* Structure describing a user specification and list thereof.
|
||||
@@ -158,6 +159,7 @@ struct userspec {
|
||||
TAILQ_ENTRY(userspec) entries;
|
||||
struct member_list users; /* list of users */
|
||||
struct privilege_list privileges; /* list of privileges */
|
||||
struct comment_list comments; /* optional comments */
|
||||
int lineno;
|
||||
char *file;
|
||||
};
|
||||
@@ -209,6 +211,11 @@ struct runascontainer {
|
||||
struct member *runasgroups;
|
||||
};
|
||||
|
||||
struct comment {
|
||||
STAILQ_ENTRY(comment) entries;
|
||||
char *str;
|
||||
};
|
||||
|
||||
/*
|
||||
* Generic structure to hold {User,Host,Runas,Cmnd}_Alias
|
||||
* 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 */
|
||||
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_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_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);
|
||||
|
@@ -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
|
||||
objectClass: top
|
||||
objectClass: sudoRole
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
objectClass: top
|
||||
objectClass: sudoRole
|
||||
|
@@ -1436,6 +1436,7 @@ sss_to_sudoers(struct sudo_sss_handle *handle, struct sss_sudo_result *sss_resul
|
||||
goto oom;
|
||||
TAILQ_INIT(&us->users);
|
||||
TAILQ_INIT(&us->privileges);
|
||||
STAILQ_INIT(&us->comments);
|
||||
TAILQ_INSERT_TAIL(sss_userspecs, us, entries);
|
||||
|
||||
/* The user has already matched, use ALL as wildcard. */
|
||||
|
Reference in New Issue
Block a user