Allow a list of digests to be specified for a command.

This commit is contained in:
Todd C. Miller
2020-03-11 11:17:52 -06:00
parent 8c08f5ef03
commit 4eca443246
19 changed files with 662 additions and 520 deletions

View File

@@ -887,6 +887,9 @@ Digest_Spec ::= "sha224" ':' digest |
"sha384" ':' digest | "sha384" ':' digest |
"sha512" ':' digest "sha512" ':' digest
Digest_List ::= Digest_Spec |
Digest_Spec ',' Digest_List
Cmnd_List ::= Cmnd | Cmnd_List ::= Cmnd |
Cmnd ',' Cmnd_List Cmnd ',' Cmnd_List
@@ -894,7 +897,7 @@ command name ::= file name |
file name args | file name args |
file name '""' file name '""'
Cmnd ::= Digest_Spec? '!'* command name | Cmnd ::= Digest_List? '!'* command name |
'!'* directory | '!'* directory |
'!'* "sudoedit" | '!'* "sudoedit" |
'!'* Cmnd_Alias '!'* Cmnd_Alias
@@ -967,12 +970,17 @@ A fully-qualified path for
is treated as an error by is treated as an error by
\fBvisudo\fR. \fBvisudo\fR.
.PP .PP
If a A
\fRcommand name\fR \fRcommand name\fR
is prefixed with a may be preceded by a
\fRDigest_Spec\fR, \fRDigest_List\fR,
the command will only match successfully if it can be verified a comma-separated list of one or more
using the specified SHA-2 digest. \fRDigest_Spec\fR
entries.
If a
\fRDigest_List\fR
is present, the command will only match successfully if it can be verified
using one of the SHA-2 digests in the list.
The following digest formats are supported: sha224, sha256, sha384 and sha512. The following digest formats are supported: sha224, sha256, sha384 and sha512.
The string may be specified in either hex or base64 format The string may be specified in either hex or base64 format
(base64 is more compact). (base64 is more compact).

View File

@@ -852,6 +852,9 @@ Digest_Spec ::= "sha224" ':' digest |
"sha384" ':' digest | "sha384" ':' digest |
"sha512" ':' digest "sha512" ':' digest
Digest_List ::= Digest_Spec |
Digest_Spec ',' Digest_List
Cmnd_List ::= Cmnd | Cmnd_List ::= Cmnd |
Cmnd ',' Cmnd_List Cmnd ',' Cmnd_List
@@ -859,7 +862,7 @@ command name ::= file name |
file name args | file name args |
file name '""' file name '""'
Cmnd ::= Digest_Spec? '!'* command name | Cmnd ::= Digest_List? '!'* command name |
'!'* directory | '!'* directory |
'!'* "sudoedit" | '!'* "sudoedit" |
'!'* Cmnd_Alias '!'* Cmnd_Alias
@@ -931,12 +934,17 @@ A fully-qualified path for
is treated as an error by is treated as an error by
.Nm visudo . .Nm visudo .
.Pp .Pp
If a A
.Li command name .Li command name
is prefixed with a may be preceded by a
.Li Digest_Spec , .Li Digest_List ,
the command will only match successfully if it can be verified a comma-separated list of one or more
using the specified SHA-2 digest. .Li Digest_Spec
entries.
If a
.Li Digest_List
is present, the command will only match successfully if it can be verified
using one of the SHA-2 digests in the list.
The following digest formats are supported: sha224, sha256, sha384 and sha512. The following digest formats are supported: sha224, sha256, sha384 and sha512.
The string may be specified in either hex or base64 format The string may be specified in either hex or base64 format
(base64 is more compact). (base64 is more compact).

View File

@@ -70,6 +70,7 @@ static void
print_command_json(struct json_container *json, const char *name, bool negated) print_command_json(struct json_container *json, const char *name, bool negated)
{ {
struct sudo_command *c = (struct sudo_command *)name; struct sudo_command *c = (struct sudo_command *)name;
struct command_digest *digest;
struct json_value value; struct json_value value;
char *cmnd = c->cmnd; char *cmnd = c->cmnd;
const char *digest_name; const char *digest_name;
@@ -85,7 +86,7 @@ print_command_json(struct json_container *json, const char *name, bool negated)
value.type = JSON_STRING; value.type = JSON_STRING;
value.u.string = cmnd; value.u.string = cmnd;
if (!negated && c->digest == NULL) { if (!negated && TAILQ_EMPTY(&c->digests)) {
/* Print as { "command": "command and args" } */ /* Print as { "command": "command and args" } */
sudo_json_add_value_as_object(json, "command", &value); sudo_json_add_value_as_object(json, "command", &value);
} else { } else {
@@ -93,11 +94,11 @@ print_command_json(struct json_container *json, const char *name, bool negated)
sudo_json_open_object(json, NULL); sudo_json_open_object(json, NULL);
sudo_json_add_value(json, "command", &value); sudo_json_add_value(json, "command", &value);
/* Optional digest. */ /* Optional digest list. */
if (c->digest != NULL) { TAILQ_FOREACH(digest, &c->digests, entries) {
digest_name = digest_type_to_name(c->digest->digest_type); digest_name = digest_type_to_name(digest->digest_type);
value.type = JSON_STRING; value.type = JSON_STRING;
value.u.string = c->digest->digest_str; value.u.string = digest->digest_str;
sudo_json_add_value(json, digest_name, &value); sudo_json_add_value(json, digest_name, &value);
} }

View File

@@ -209,6 +209,52 @@ print_global_defaults_ldif(FILE *fp, struct sudoers_parse_tree *parse_tree,
debug_return_bool(!ferror(fp)); debug_return_bool(!ferror(fp));
} }
/*
* Format a sudo_command as a string.
* Returns the formatted, dynamically allocated string or dies on error.
*/
static char *
format_cmnd(struct sudo_command *c, bool negated)
{
struct command_digest *digest;
char *buf, *cp;
size_t bufsiz;
int len;
debug_decl(format_cmnd, SUDOERS_DEBUG_UTIL);
bufsiz = negated + strlen(c->cmnd) + 1;
if (c->args != NULL)
bufsiz += 1 + strlen(c->args);
TAILQ_FOREACH(digest, &c->digests, entries) {
bufsiz += strlen(digest_type_to_name(digest->digest_type)) + 1 +
strlen(digest->digest_str) + 1;
if (TAILQ_NEXT(digest, entries) != NULL)
bufsiz += 2;
}
if ((buf = malloc(bufsiz)) == NULL) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
cp = buf;
TAILQ_FOREACH(digest, &c->digests, entries) {
len = snprintf(cp, bufsiz - (cp - buf), "%s:%s%s ",
digest_type_to_name(digest->digest_type), digest->digest_str,
TAILQ_NEXT(digest, entries) ? "," : "");
if (len < 0 || len >= (int)bufsiz - (cp - buf))
sudo_fatalx(U_("internal error, %s overflow"), __func__);
cp += len;
}
len = snprintf(cp, bufsiz - (cp - buf), "%s%s%s%s", negated ? "!" : "",
c->cmnd, c->args ? " " : "", c->args ? c->args : "");
if (len < 0 || len >= (int)bufsiz - (cp - buf))
sudo_fatalx(U_("internal error, %s overflow"), __func__);
debug_return_str(buf);
}
/* /*
* Print struct member in LDIF format as the specified attribute. * Print struct member in LDIF format as the specified attribute.
* See print_member_int() in parse.c. * See print_member_int() in parse.c.
@@ -219,7 +265,6 @@ print_member_ldif(FILE *fp, struct sudoers_parse_tree *parse_tree, char *name,
{ {
struct alias *a; struct alias *a;
struct member *m; struct member *m;
struct sudo_command *c;
char *attr_val; char *attr_val;
int len; int len;
debug_decl(print_member_ldif, SUDOERS_DEBUG_UTIL); debug_decl(print_member_ldif, SUDOERS_DEBUG_UTIL);
@@ -233,16 +278,7 @@ print_member_ldif(FILE *fp, struct sudoers_parse_tree *parse_tree, char *name,
print_attribute_ldif(fp, attr_name, ""); print_attribute_ldif(fp, attr_name, "");
break; break;
case COMMAND: case COMMAND:
c = (struct sudo_command *)name; attr_val = format_cmnd((struct sudo_command *)name, negated);
len = asprintf(&attr_val, "%s%s%s%s%s%s%s%s",
c->digest ? digest_type_to_name(c->digest->digest_type) : "",
c->digest ? ":" : "", c->digest ? c->digest->digest_str : "",
c->digest ? " " : "", negated ? "!" : "", c->cmnd,
c->args ? " " : "", c->args ? c->args : "");
if (len == -1) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
print_attribute_ldif(fp, attr_name, attr_val); print_attribute_ldif(fp, attr_name, attr_val);
free(attr_val); free(attr_val);
break; break;

View File

@@ -51,6 +51,7 @@ sudoers_format_member_int(struct sudo_lbuf *lbuf,
struct alias *a; struct alias *a;
struct member *m; struct member *m;
struct sudo_command *c; struct sudo_command *c;
struct command_digest *digest;
debug_decl(sudoers_format_member_int, SUDOERS_DEBUG_UTIL); debug_decl(sudoers_format_member_int, SUDOERS_DEBUG_UTIL);
switch (type) { switch (type) {
@@ -63,10 +64,10 @@ sudoers_format_member_int(struct sudo_lbuf *lbuf,
break; break;
case COMMAND: case COMMAND:
c = (struct sudo_command *) name; c = (struct sudo_command *) name;
if (c->digest != NULL) { TAILQ_FOREACH(digest, &c->digests, entries) {
sudo_lbuf_append(lbuf, "%s:%s ", sudo_lbuf_append(lbuf, "%s:%s%s ",
digest_type_to_name(c->digest->digest_type), digest_type_to_name(digest->digest_type),
c->digest->digest_str); digest->digest_str, TAILQ_NEXT(digest, entries) ? "," : "");
} }
if (negated) if (negated)
sudo_lbuf_append(lbuf, "!"); sudo_lbuf_append(lbuf, "!");

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
/* /*
* SPDX-License-Identifier: ISC * SPDX-License-Identifier: ISC
* *
* Copyright (c) 1996, 1998-2005, 2007-2013, 2014-2018 * Copyright (c) 1996, 1998-2005, 2007-2013, 2014-2020
* Todd C. Miller <Todd.Miller@sudo.ws> * Todd C. Miller <Todd.Miller@sudo.ws>
* *
* Permission to use, copy, modify, and distribute this software for any * Permission to use, copy, modify, and distribute this software for any
@@ -174,7 +174,8 @@ static struct command_digest *new_digest(int, char *);
%type <string> timeoutspec %type <string> timeoutspec
%type <string> notbeforespec %type <string> notbeforespec
%type <string> notafterspec %type <string> notafterspec
%type <digest> digest %type <digest> digestspec
%type <digest> digestlist
%% %%
@@ -451,7 +452,7 @@ cmndspec : runasspec options cmndtag digcmnd {
} }
; ;
digest : SHA224_TOK ':' DIGEST { digestspec : SHA224_TOK ':' DIGEST {
$$ = new_digest(SUDO_DIGEST_SHA224, $3); $$ = new_digest(SUDO_DIGEST_SHA224, $3);
if ($$ == NULL) { if ($$ == NULL) {
sudoerserror(N_("unable to allocate memory")); sudoerserror(N_("unable to allocate memory"));
@@ -481,16 +482,25 @@ digest : SHA224_TOK ':' DIGEST {
} }
; ;
digestlist : digestspec
| digestlist ',' digestspec {
HLTQ_CONCAT($1, $3, entries);
$$ = $1;
}
;
digcmnd : opcmnd { digcmnd : opcmnd {
$$ = $1; $$ = $1;
} }
| digest opcmnd { | digestlist opcmnd {
struct sudo_command *c =
(struct sudo_command *) $2->name;
if ($2->type != COMMAND) { if ($2->type != COMMAND) {
sudoerserror(N_("a digest requires a path name")); sudoerserror(N_("a digest requires a path name"));
YYERROR; YYERROR;
} }
/* XXX - yuck */ HLTQ_TO_TAILQ(&c->digests, $1, entries);
((struct sudo_command *) $2->name)->digest = $1;
$$ = $2; $$ = $2;
} }
; ;
@@ -730,6 +740,7 @@ cmnd : ALL {
} }
c->cmnd = $1.cmnd; c->cmnd = $1.cmnd;
c->args = $1.args; c->args = $1.args;
TAILQ_INIT(&c->digests);
$$ = new_member((char *)c, COMMAND); $$ = new_member((char *)c, COMMAND);
if ($$ == NULL) { if ($$ == NULL) {
free(c); free(c);
@@ -992,6 +1003,7 @@ new_digest(int digest_type, char *digest_str)
debug_return_ptr(NULL); debug_return_ptr(NULL);
} }
HLTQ_INIT(digest, entries);
digest->digest_type = digest_type; digest->digest_type = digest_type;
digest->digest_str = digest_str; digest->digest_str = digest_str;
if (digest->digest_str == NULL) { if (digest->digest_str == NULL) {
@@ -1080,13 +1092,15 @@ free_member(struct member *m)
debug_decl(free_member, SUDOERS_DEBUG_PARSER); debug_decl(free_member, SUDOERS_DEBUG_PARSER);
if (m->type == COMMAND) { if (m->type == COMMAND) {
struct sudo_command *c = (struct sudo_command *)m->name; struct command_digest *digest;
free(c->cmnd); struct sudo_command *c = (struct sudo_command *)m->name;
free(c->args); free(c->cmnd);
if (c->digest != NULL) { free(c->args);
free(c->digest->digest_str); while ((digest = TAILQ_FIRST(&c->digests)) != NULL) {
free(c->digest); TAILQ_REMOVE(&c->digests, digest, entries);
} free(digest->digest_str);
free(digest);
}
} }
free(m->name); free(m->name);
free(m); free(m);

View File

@@ -288,6 +288,110 @@ oom:
debug_return_bool(false); debug_return_bool(false);
} }
/*
* If a digest prefix is present, add it to struct command_digest_list
* and update cmnd to point to the command after the digest.
* Returns 1 if a digest was parsed, 0 if not and -1 on error.
*/
static int
sudo_ldap_extract_digest(char **cmnd, struct command_digest_list *digests)
{
char *ep, *cp = *cmnd;
struct command_digest *digest;
int digest_type = SUDO_DIGEST_INVALID;
debug_decl(sudo_ldap_extract_digest, SUDOERS_DEBUG_LDAP);
/*
* Check for and extract a digest prefix, e.g.
* sha224:d06a2617c98d377c250edd470fd5e576327748d82915d6e33b5f8db1 /bin/ls
*/
if (cp[0] == 's' && cp[1] == 'h' && cp[2] == 'a') {
switch (cp[3]) {
case '2':
if (cp[4] == '2' && cp[5] == '4')
digest_type = SUDO_DIGEST_SHA224;
else if (cp[4] == '5' && cp[5] == '6')
digest_type = SUDO_DIGEST_SHA256;
break;
case '3':
if (cp[4] == '8' && cp[5] == '4')
digest_type = SUDO_DIGEST_SHA384;
break;
case '5':
if (cp[4] == '1' && cp[5] == '2')
digest_type = SUDO_DIGEST_SHA512;
break;
}
if (digest_type != SUDO_DIGEST_INVALID) {
cp += 6;
while (isblank((unsigned char)*cp))
cp++;
if (*cp == ':') {
cp++;
while (isblank((unsigned char)*cp))
cp++;
ep = cp;
while (*ep != '\0' && !isblank((unsigned char)*ep) && *ep != ',')
ep++;
if (isblank((unsigned char)*ep) || *ep == ',') {
if ((digest = malloc(sizeof(*digest))) == NULL) {
sudo_warnx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
debug_return_int(-1);
}
digest->digest_type = digest_type;
digest->digest_str = strndup(cp, (size_t)(ep - cp));
if (digest->digest_str == NULL) {
sudo_warnx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
free(digest);
debug_return_int(-1);
}
while (isblank((unsigned char)*ep))
ep++;
*cmnd = ep;
sudo_debug_printf(SUDO_DEBUG_INFO,
"%s digest %s for %s",
digest_type_to_name(digest_type),
digest->digest_str, cp);
TAILQ_INSERT_TAIL(digests, digest, entries);
debug_return_int(1);
}
}
}
}
debug_return_int(0);
}
/*
* If a digest list is present, fill in struct command_digest_list
* and update cmnd to point to the command after the digest.
* Returns false on error, else true.
*/
static bool
sudo_ldap_extract_digests(char **cmnd, struct command_digest_list *digests)
{
char *cp = *cmnd;
int rc;
debug_decl(sudo_ldap_extract_digests, SUDOERS_DEBUG_LDAP);
for (;;) {
rc = sudo_ldap_extract_digest(&cp, digests);
if (rc != 1)
break;
/* Check for additional digestspecs, separated by a comma. */
if (*cp != ',')
break;
do {
cp++;
} while (isblank((unsigned char)*cp));
}
*cmnd = cp;
debug_return_bool(rc != -1);
}
/* /*
* Convert an LDAP sudoRole to a sudoers privilege. * Convert an LDAP sudoRole to a sudoers privilege.
* Pass in struct berval ** for LDAP or char *** for SSSD. * Pass in struct berval ** for LDAP or char *** for SSSD.
@@ -358,6 +462,7 @@ sudo_ldap_role_to_priv(const char *cn, void *hosts, void *runasusers,
goto oom; goto oom;
} }
m->name = (char *)c; m->name = (char *)c;
TAILQ_INIT(&c->digests);
} }
/* Negated commands have precedence so insert them at the end. */ /* Negated commands have precedence so insert them at the end. */
@@ -481,17 +586,13 @@ sudo_ldap_role_to_priv(const char *cn, void *hosts, void *runasusers,
if (cmndspec->tags.setenv == UNSPEC) if (cmndspec->tags.setenv == UNSPEC)
cmndspec->tags.setenv = IMPLIED; cmndspec->tags.setenv = IMPLIED;
} else { } else {
struct command_digest digest;
char *args; char *args;
m->type = COMMAND; m->type = COMMAND;
/* Fill in command with optional digest. */ /* Fill in command with optional digests. */
if (sudo_ldap_extract_digest(&cmnd, &digest) != NULL) { if (!sudo_ldap_extract_digests(&cmnd, &c->digests))
if ((c->digest = malloc(sizeof(*c->digest))) == NULL) goto oom;
goto oom;
*c->digest = digest;
}
if ((args = strpbrk(cmnd, " \t")) != NULL) { if ((args = strpbrk(cmnd, " \t")) != NULL) {
*args++ = '\0'; *args++ = '\0';
if ((c->args = strdup(args)) == NULL) if ((c->args = strdup(args)) == NULL)
@@ -515,70 +616,3 @@ oom:
} }
debug_return_ptr(NULL); debug_return_ptr(NULL);
} }
/*
* If a digest prefix is present, fills in struct command_digest
* and returns a pointer to it, updating cmnd to point to the
* command after the digest.
*/
struct command_digest *
sudo_ldap_extract_digest(char **cmnd, struct command_digest *digest)
{
char *ep, *cp = *cmnd;
int digest_type = SUDO_DIGEST_INVALID;
debug_decl(sudo_ldap_check_command, SUDOERS_DEBUG_LDAP);
/*
* Check for and extract a digest prefix, e.g.
* sha224:d06a2617c98d377c250edd470fd5e576327748d82915d6e33b5f8db1 /bin/ls
*/
if (cp[0] == 's' && cp[1] == 'h' && cp[2] == 'a') {
switch (cp[3]) {
case '2':
if (cp[4] == '2' && cp[5] == '4')
digest_type = SUDO_DIGEST_SHA224;
else if (cp[4] == '5' && cp[5] == '6')
digest_type = SUDO_DIGEST_SHA256;
break;
case '3':
if (cp[4] == '8' && cp[5] == '4')
digest_type = SUDO_DIGEST_SHA384;
break;
case '5':
if (cp[4] == '1' && cp[5] == '2')
digest_type = SUDO_DIGEST_SHA512;
break;
}
if (digest_type != SUDO_DIGEST_INVALID) {
cp += 6;
while (isblank((unsigned char)*cp))
cp++;
if (*cp == ':') {
cp++;
while (isblank((unsigned char)*cp))
cp++;
ep = cp;
while (*ep != '\0' && !isblank((unsigned char)*ep))
ep++;
if (*ep != '\0') {
digest->digest_type = digest_type;
digest->digest_str = strndup(cp, (size_t)(ep - cp));
if (digest->digest_str == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
debug_return_ptr(NULL);
}
cp = ep + 1;
while (isblank((unsigned char)*cp))
cp++;
*cmnd = cp;
sudo_debug_printf(SUDO_DEBUG_INFO,
"%s digest %s for %s",
digest_type_to_name(digest_type),
digest->digest_str, cp);
debug_return_ptr(digest);
}
}
}
}
debug_return_ptr(NULL);
}

View File

@@ -411,7 +411,7 @@ cmnd_matches(struct sudoers_parse_tree *parse_tree, const struct member *m)
break; break;
case COMMAND: case COMMAND:
c = (struct sudo_command *)m->name; c = (struct sudo_command *)m->name;
if (command_matches(c->cmnd, c->args, c->digest)) if (command_matches(c->cmnd, c->args, &c->digests))
matched = !m->negated; matched = !m->negated;
break; break;
} }

View File

@@ -126,18 +126,18 @@ is_script(int fd)
* Returns false on error, else true. * Returns false on error, else true.
*/ */
static bool static bool
open_cmnd(const char *path, const struct command_digest *digest, int *fdp) open_cmnd(const char *path, const struct command_digest_list *digests, int *fdp)
{ {
int fd = -1; int fd = -1;
debug_decl(open_cmnd, SUDOERS_DEBUG_MATCH); debug_decl(open_cmnd, SUDOERS_DEBUG_MATCH);
/* Only open the file for fdexec or for digest matching. */ /* Only open the file for fdexec or for digest matching. */
if (def_fdexec != always && digest == NULL) if (def_fdexec != always && TAILQ_EMPTY(digests))
debug_return_bool(true); debug_return_bool(true);
fd = open(path, O_RDONLY|O_NONBLOCK); fd = open(path, O_RDONLY|O_NONBLOCK);
# ifdef O_EXEC # ifdef O_EXEC
if (fd == -1 && errno == EACCES && digest == NULL) { if (fd == -1 && errno == EACCES && TAILQ_EMPTY(digests))
/* Try again with O_EXEC if no digest is specified. */ /* Try again with O_EXEC if no digest is specified. */
const int saved_errno = errno; const int saved_errno = errno;
if ((fd = open(path, O_EXEC)) == -1) if ((fd = open(path, O_EXEC)) == -1)
@@ -197,7 +197,7 @@ set_cmnd_fd(int fd)
*/ */
static bool static bool
command_matches_dir(const char *sudoers_dir, size_t dlen, command_matches_dir(const char *sudoers_dir, size_t dlen,
const struct command_digest *digest) const struct command_digest_list *digests)
{ {
struct stat sudoers_stat; struct stat sudoers_stat;
struct dirent *dent; struct dirent *dent;
@@ -233,7 +233,7 @@ command_matches_dir(const char *sudoers_dir, size_t dlen,
continue; continue;
/* Open the file for fdexec or for digest matching. */ /* Open the file for fdexec or for digest matching. */
if (!open_cmnd(buf, digest, &fd)) if (!open_cmnd(buf, digests, &fd))
continue; continue;
if (!do_stat(fd, buf, &sudoers_stat)) if (!do_stat(fd, buf, &sudoers_stat))
continue; continue;
@@ -241,7 +241,7 @@ command_matches_dir(const char *sudoers_dir, size_t dlen,
if (user_stat == NULL || if (user_stat == NULL ||
(user_stat->st_dev == sudoers_stat.st_dev && (user_stat->st_dev == sudoers_stat.st_dev &&
user_stat->st_ino == sudoers_stat.st_ino)) { user_stat->st_ino == sudoers_stat.st_ino)) {
if (digest != NULL && !digest_matches(fd, buf, digest)) if (!digest_matches(fd, buf, digests))
continue; continue;
free(safe_cmnd); free(safe_cmnd);
if ((safe_cmnd = strdup(buf)) == NULL) { if ((safe_cmnd = strdup(buf)) == NULL) {
@@ -265,7 +265,7 @@ command_matches_dir(const char *sudoers_dir, size_t dlen,
static bool static bool
command_matches_fnmatch(const char *sudoers_cmnd, const char *sudoers_args, command_matches_fnmatch(const char *sudoers_cmnd, const char *sudoers_args,
const struct command_digest *digest) const struct command_digest_list *digests)
{ {
struct stat sb; /* XXX - unused */ struct stat sb; /* XXX - unused */
int fd = -1; int fd = -1;
@@ -282,12 +282,12 @@ command_matches_fnmatch(const char *sudoers_cmnd, const char *sudoers_args,
debug_return_bool(false); debug_return_bool(false);
if (command_args_match(sudoers_cmnd, sudoers_args)) { if (command_args_match(sudoers_cmnd, sudoers_args)) {
/* Open the file for fdexec or for digest matching. */ /* Open the file for fdexec or for digest matching. */
if (!open_cmnd(user_cmnd, digest, &fd)) if (!open_cmnd(user_cmnd, digests, &fd))
goto bad; goto bad;
if (!do_stat(fd, user_cmnd, &sb)) if (!do_stat(fd, user_cmnd, &sb))
goto bad; goto bad;
/* Check digest of user_cmnd since sudoers_cmnd is a pattern. */ /* Check digest of user_cmnd since sudoers_cmnd is a pattern. */
if (digest != NULL && !digest_matches(fd, user_cmnd, digest)) if (!digest_matches(fd, user_cmnd, digests))
goto bad; goto bad;
set_cmnd_fd(fd); set_cmnd_fd(fd);
@@ -305,7 +305,7 @@ bad:
static bool static bool
command_matches_glob(const char *sudoers_cmnd, const char *sudoers_args, command_matches_glob(const char *sudoers_cmnd, const char *sudoers_args,
const struct command_digest *digest) const struct command_digest_list *digests)
{ {
struct stat sudoers_stat; struct stat sudoers_stat;
bool bad_digest = false; bool bad_digest = false;
@@ -349,7 +349,7 @@ command_matches_glob(const char *sudoers_cmnd, const char *sudoers_args,
if (strcmp(cp, user_cmnd) != 0) if (strcmp(cp, user_cmnd) != 0)
continue; continue;
/* Open the file for fdexec or for digest matching. */ /* Open the file for fdexec or for digest matching. */
if (!open_cmnd(cp, digest, &fd)) if (!open_cmnd(cp, digests, &fd))
continue; continue;
if (!do_stat(fd, cp, &sudoers_stat)) if (!do_stat(fd, cp, &sudoers_stat))
continue; continue;
@@ -357,7 +357,7 @@ command_matches_glob(const char *sudoers_cmnd, const char *sudoers_args,
(user_stat->st_dev == sudoers_stat.st_dev && (user_stat->st_dev == sudoers_stat.st_dev &&
user_stat->st_ino == sudoers_stat.st_ino)) { user_stat->st_ino == sudoers_stat.st_ino)) {
/* There could be multiple matches, check digest early. */ /* There could be multiple matches, check digest early. */
if (digest != NULL && !digest_matches(fd, cp, digest)) { if (!digest_matches(fd, cp, digests)) {
bad_digest = true; bad_digest = true;
continue; continue;
} }
@@ -385,7 +385,7 @@ command_matches_glob(const char *sudoers_cmnd, const char *sudoers_args,
/* If it ends in '/' it is a directory spec. */ /* If it ends in '/' it is a directory spec. */
dlen = strlen(cp); dlen = strlen(cp);
if (cp[dlen - 1] == '/') { if (cp[dlen - 1] == '/') {
if (command_matches_dir(cp, dlen, digest)) if (command_matches_dir(cp, dlen, digests))
debug_return_bool(true); debug_return_bool(true);
continue; continue;
} }
@@ -399,14 +399,14 @@ command_matches_glob(const char *sudoers_cmnd, const char *sudoers_args,
continue; continue;
/* Open the file for fdexec or for digest matching. */ /* Open the file for fdexec or for digest matching. */
if (!open_cmnd(cp, digest, &fd)) if (!open_cmnd(cp, digests, &fd))
continue; continue;
if (!do_stat(fd, cp, &sudoers_stat)) if (!do_stat(fd, cp, &sudoers_stat))
continue; continue;
if (user_stat == NULL || if (user_stat == NULL ||
(user_stat->st_dev == sudoers_stat.st_dev && (user_stat->st_dev == sudoers_stat.st_dev &&
user_stat->st_ino == sudoers_stat.st_ino)) { user_stat->st_ino == sudoers_stat.st_ino)) {
if (digest != NULL && !digest_matches(fd, cp, digest)) if (!digest_matches(fd, cp, digests))
continue; continue;
free(safe_cmnd); free(safe_cmnd);
if ((safe_cmnd = strdup(cp)) == NULL) { if ((safe_cmnd = strdup(cp)) == NULL) {
@@ -433,7 +433,7 @@ done:
} }
static bool static bool
command_matches_normal(const char *sudoers_cmnd, const char *sudoers_args, const struct command_digest *digest) command_matches_normal(const char *sudoers_cmnd, const char *sudoers_args, const struct command_digest_list *digests)
{ {
struct stat sudoers_stat; struct stat sudoers_stat;
const char *base; const char *base;
@@ -444,7 +444,7 @@ command_matches_normal(const char *sudoers_cmnd, const char *sudoers_args, const
/* If it ends in '/' it is a directory spec. */ /* If it ends in '/' it is a directory spec. */
dlen = strlen(sudoers_cmnd); dlen = strlen(sudoers_cmnd);
if (sudoers_cmnd[dlen - 1] == '/') if (sudoers_cmnd[dlen - 1] == '/')
debug_return_bool(command_matches_dir(sudoers_cmnd, dlen, digest)); debug_return_bool(command_matches_dir(sudoers_cmnd, dlen, digests));
/* Only proceed if user_base and basename(sudoers_cmnd) match */ /* Only proceed if user_base and basename(sudoers_cmnd) match */
if ((base = strrchr(sudoers_cmnd, '/')) == NULL) if ((base = strrchr(sudoers_cmnd, '/')) == NULL)
@@ -455,7 +455,7 @@ command_matches_normal(const char *sudoers_cmnd, const char *sudoers_args, const
debug_return_bool(false); debug_return_bool(false);
/* Open the file for fdexec or for digest matching. */ /* Open the file for fdexec or for digest matching. */
if (!open_cmnd(sudoers_cmnd, digest, &fd)) if (!open_cmnd(sudoers_cmnd, digests, &fd))
goto bad; goto bad;
/* /*
@@ -476,7 +476,7 @@ command_matches_normal(const char *sudoers_cmnd, const char *sudoers_args, const
} }
if (!command_args_match(sudoers_cmnd, sudoers_args)) if (!command_args_match(sudoers_cmnd, sudoers_args))
goto bad; goto bad;
if (digest != NULL && !digest_matches(fd, sudoers_cmnd, digest)) { if (!digest_matches(fd, sudoers_cmnd, digests)) {
/* XXX - log functions not available but we should log very loudly */ /* XXX - log functions not available but we should log very loudly */
goto bad; goto bad;
} }
@@ -498,7 +498,7 @@ bad:
* otherwise, return true if user_cmnd names one of the inodes in path. * otherwise, return true if user_cmnd names one of the inodes in path.
*/ */
bool bool
command_matches(const char *sudoers_cmnd, const char *sudoers_args, const struct command_digest *digest) command_matches(const char *sudoers_cmnd, const char *sudoers_args, const struct command_digest_list *digests)
{ {
bool rc = false; bool rc = false;
debug_decl(command_matches, SUDOERS_DEBUG_MATCH); debug_decl(command_matches, SUDOERS_DEBUG_MATCH);
@@ -526,11 +526,11 @@ command_matches(const char *sudoers_cmnd, const char *sudoers_args, const struct
* use glob(3) and/or fnmatch(3) to do the matching. * use glob(3) and/or fnmatch(3) to do the matching.
*/ */
if (def_fast_glob) if (def_fast_glob)
rc = command_matches_fnmatch(sudoers_cmnd, sudoers_args, digest); rc = command_matches_fnmatch(sudoers_cmnd, sudoers_args, digests);
else else
rc = command_matches_glob(sudoers_cmnd, sudoers_args, digest); rc = command_matches_glob(sudoers_cmnd, sudoers_args, digests);
} else { } else {
rc = command_matches_normal(sudoers_cmnd, sudoers_args, digest); rc = command_matches_normal(sudoers_cmnd, sudoers_args, digests);
} }
done: done:
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO, sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,

View File

@@ -1,7 +1,7 @@
/* /*
* SPDX-License-Identifier: ISC * SPDX-License-Identifier: ISC
* *
* Copyright (c) 1996, 1998-2005, 2007-2019 * Copyright (c) 1996, 1998-2005, 2007-2020
* Todd C. Miller <Todd.Miller@sudo.ws> * Todd C. Miller <Todd.Miller@sudo.ws>
* *
* Permission to use, copy, modify, and distribute this software for any * Permission to use, copy, modify, and distribute this software for any
@@ -41,61 +41,81 @@
#include <unistd.h> #include <unistd.h>
#include "sudoers.h" #include "sudoers.h"
#include "sudo_digest.h"
#include <gram.h> #include <gram.h>
bool bool
digest_matches(int fd, const char *file, const struct command_digest *digest) digest_matches(int fd, const char *file, const struct command_digest_list *digests)
{ {
unsigned int digest_type = SUDO_DIGEST_INVALID;
unsigned char *file_digest = NULL; unsigned char *file_digest = NULL;
unsigned char *sudoers_digest = NULL; unsigned char *sudoers_digest = NULL;
struct command_digest *digest;
bool matched = false; bool matched = false;
size_t digest_len; size_t digest_len;
debug_decl(digest_matches, SUDOERS_DEBUG_MATCH); debug_decl(digest_matches, SUDOERS_DEBUG_MATCH);
if (fd == -1) if (TAILQ_EMPTY(digests)) {
goto done; /* No digest, no problem. */
debug_return_bool(true);
file_digest = sudo_filedigest(fd, file, digest->digest_type, &digest_len);
if (lseek(fd, (off_t)0, SEEK_SET) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
"unable to rewind digest fd");
} }
if (file_digest == NULL) {
/* Warning (if any) printed by sudo_filedigest() */ if (fd == -1) {
/* No file, no match. */
goto done; goto done;
} }
/* Convert the command digest from ascii to binary. */ TAILQ_FOREACH(digest, digests, entries) {
if ((sudoers_digest = malloc(digest_len)) == NULL) { /* Compute file digest if needed. */
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); if (digest->digest_type != digest_type) {
goto done; free(file_digest);
} file_digest = sudo_filedigest(fd, file, digest->digest_type,
if (strlen(digest->digest_str) == digest_len * 2) { &digest_len);
/* Convert ascii hex to binary. */ if (lseek(fd, (off_t)0, SEEK_SET) == -1) {
unsigned int i; sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
for (i = 0; i < digest_len; i++) { "unable to rewind digest fd");
const int h = hexchar(&digest->digest_str[i + i]); }
if (h == -1) if (file_digest == NULL) {
/* Warning (if any) printed by sudo_filedigest() */
goto done;
}
digest_type = digest->digest_type;
}
/* Convert the command digest from ascii to binary. */
if ((sudoers_digest = malloc(digest_len)) == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
goto done;
}
if (strlen(digest->digest_str) == digest_len * 2) {
/* Convert ascii hex to binary. */
unsigned int i;
for (i = 0; i < digest_len; i++) {
const int h = hexchar(&digest->digest_str[i + i]);
if (h == -1)
goto bad_format;
sudoers_digest[i] = (unsigned char)h;
}
} else {
/* Convert base64 to binary. */
size_t len = base64_decode(digest->digest_str, sudoers_digest, digest_len);
if (len != digest_len) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"incorrect length for digest, expected %zu, got %zu",
digest_len, len);
goto bad_format; goto bad_format;
sudoers_digest[i] = (unsigned char)h; }
} }
} else { if (memcmp(file_digest, sudoers_digest, digest_len) == 0) {
/* Convert base64 to binary. */ matched = true;
size_t len = base64_decode(digest->digest_str, sudoers_digest, digest_len); break;
if (len != digest_len) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"incorrect length for digest, expected %zu, got %zu",
digest_len, len);
goto bad_format;
} }
}
if (memcmp(file_digest, sudoers_digest, digest_len) == 0) {
matched = true;
} else {
sudo_debug_printf(SUDO_DEBUG_DIAG|SUDO_DEBUG_LINENO, sudo_debug_printf(SUDO_DEBUG_DIAG|SUDO_DEBUG_LINENO,
"%s digest mismatch for %s, expecting %s", "%s digest mismatch for %s, expecting %s",
digest_type_to_name(digest->digest_type), file, digest->digest_str); digest_type_to_name(digest->digest_type), file, digest->digest_str);
free(sudoers_digest);
sudoers_digest = NULL;
} }
goto done; goto done;

View File

@@ -1,7 +1,7 @@
/* /*
* SPDX-License-Identifier: ISC * SPDX-License-Identifier: ISC
* *
* Copyright (c) 1996, 1998-2000, 2004, 2007-2018 * Copyright (c) 1996, 1998-2000, 2004, 2007-2020
* Todd C. Miller <Todd.Miller@sudo.ws> * Todd C. Miller <Todd.Miller@sudo.ws>
* *
* Permission to use, copy, modify, and distribute this software for any * Permission to use, copy, modify, and distribute this software for any
@@ -105,20 +105,11 @@
(cs1)->runasgrouplist != (cs2)->runasgrouplist) (cs1)->runasgrouplist != (cs2)->runasgrouplist)
struct command_digest { struct command_digest {
TAILQ_ENTRY(command_digest) entries;
unsigned int digest_type; unsigned int digest_type;
char *digest_str; char *digest_str;
}; };
/*
* A command with option args and digest.
* XXX - merge into struct member
*/
struct sudo_command {
char *cmnd;
char *args;
struct command_digest *digest;
};
/* /*
* Tags associated with a command. * Tags associated with a command.
* Possible values: true, false, IMPLIED, UNSPEC. * Possible values: true, false, IMPLIED, UNSPEC.
@@ -164,13 +155,14 @@ struct command_options {
*/ */
/* /*
* Tail queue list head structure. * Tail queue list head structures.
*/ */
TAILQ_HEAD(defaults_list, defaults); TAILQ_HEAD(defaults_list, defaults);
TAILQ_HEAD(userspec_list, userspec); 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);
TAILQ_HEAD(command_digest_list, command_digest);
STAILQ_HEAD(comment_list, sudoers_comment); STAILQ_HEAD(comment_list, sudoers_comment);
/* /*
@@ -196,6 +188,16 @@ struct privilege {
struct defaults_list defaults; /* list of sudoOptions */ struct defaults_list defaults; /* list of sudoOptions */
}; };
/*
* A command with option args and digest.
* XXX - merge into struct member
*/
struct sudo_command {
char *cmnd;
char *args;
struct command_digest_list digests;
};
/* /*
* Structure describing a linked list of Cmnd_Specs. * Structure describing a linked list of Cmnd_Specs.
* XXX - include struct command_options instad of its contents inline * XXX - include struct command_options instad of its contents inline
@@ -306,10 +308,10 @@ void reparent_parse_tree(struct sudoers_parse_tree *new_tree);
bool addr_matches(char *n); bool addr_matches(char *n);
/* match_command.c */ /* match_command.c */
bool command_matches(const char *sudoers_cmnd, const char *sudoers_args, const struct command_digest *digest); bool command_matches(const char *sudoers_cmnd, const char *sudoers_args, const struct command_digest_list *digests);
/* match_digest.c */ /* match_digest.c */
bool digest_matches(int fd, const char *file, const struct command_digest *digest); bool digest_matches(int fd, const char *file, const struct command_digest_list *digests);
/* match.c */ /* match.c */
struct group; struct group;

View File

@@ -1,4 +1,4 @@
Cmnd_Alias LS = sha224:d06a2617c98d377c250edd470fd5e576327748d82915d6e33b5f8db1 /bin/ls Cmnd_Alias LS = sha224:d06a2617c98d377c250edd470fd5e576327748d82915d6e33b5f8db1, sha224:d7910e1967342b4605cb73a550944044c631cd3514001900966962ac /bin/ls
Cmnd_Alias SH = sha256:hOtoe/iK6SlGg7w4BfZBBdSsXjUmTJ5+ts51yjh7vkM= /bin/sh Cmnd_Alias SH = sha256:hOtoe/iK6SlGg7w4BfZBBdSsXjUmTJ5+ts51yjh7vkM=, sha256:1IXHRCxXgSnIEnb+xBz4PAfWaPdXIBWKFF0QCwxJ5G4= /bin/sh
millert ALL = LS, SH, sha512:srzYEQ2aqzm+it3f74opTMkIImZRLxBARVpb0g9RSouJYdLt7DTRMEY4Ry9NyaOiDoUIplpNjqYH0JMYPVdFnw /bin/kill millert ALL = LS, SH, sha512:srzYEQ2aqzm+it3f74opTMkIImZRLxBARVpb0g9RSouJYdLt7DTRMEY4Ry9NyaOiDoUIplpNjqYH0JMYPVdFnw /bin/kill

View File

@@ -3,13 +3,15 @@
"LS": [ "LS": [
{ {
"command": "/bin/ls", "command": "/bin/ls",
"sha224": "d06a2617c98d377c250edd470fd5e576327748d82915d6e33b5f8db1" "sha224": "d06a2617c98d377c250edd470fd5e576327748d82915d6e33b5f8db1",
"sha224": "d7910e1967342b4605cb73a550944044c631cd3514001900966962ac"
} }
], ],
"SH": [ "SH": [
{ {
"command": "/bin/sh", "command": "/bin/sh",
"sha256": "hOtoe/iK6SlGg7w4BfZBBdSsXjUmTJ5+ts51yjh7vkM=" "sha256": "hOtoe/iK6SlGg7w4BfZBBdSsXjUmTJ5+ts51yjh7vkM=",
"sha256": "1IXHRCxXgSnIEnb+xBz4PAfWaPdXIBWKFF0QCwxJ5G4="
} }
] ]
}, },

View File

@@ -4,8 +4,8 @@ objectClass: sudoRole
cn: millert cn: millert
sudoUser: millert sudoUser: millert
sudoHost: ALL sudoHost: ALL
sudoCommand: sha224:d06a2617c98d377c250edd470fd5e576327748d82915d6e33b5f8db1 /bin/ls sudoCommand: sha224:d06a2617c98d377c250edd470fd5e576327748d82915d6e33b5f8db1, sha224:d7910e1967342b4605cb73a550944044c631cd3514001900966962ac /bin/ls
sudoCommand: sha256:hOtoe/iK6SlGg7w4BfZBBdSsXjUmTJ5+ts51yjh7vkM= /bin/sh sudoCommand: sha256:hOtoe/iK6SlGg7w4BfZBBdSsXjUmTJ5+ts51yjh7vkM=, sha256:1IXHRCxXgSnIEnb+xBz4PAfWaPdXIBWKFF0QCwxJ5G4= /bin/sh
sudoCommand: sha512:srzYEQ2aqzm+it3f74opTMkIImZRLxBARVpb0g9RSouJYdLt7DTRMEY4Ry9NyaOiDoUIplpNjqYH0JMYPVdFnw /bin/kill sudoCommand: sha512:srzYEQ2aqzm+it3f74opTMkIImZRLxBARVpb0g9RSouJYdLt7DTRMEY4Ry9NyaOiDoUIplpNjqYH0JMYPVdFnw /bin/kill
sudoOrder: 1 sudoOrder: 1

View File

@@ -1,5 +1,7 @@
# sudoRole millert # sudoRole millert
millert ALL = sha224:d06a2617c98d377c250edd470fd5e576327748d82915d6e33b5f8db1\ millert ALL = sha224:d06a2617c98d377c250edd470fd5e576327748d82915d6e33b5f8db1,\
/bin/ls, sha256:hOtoe/iK6SlGg7w4BfZBBdSsXjUmTJ5+ts51yjh7vkM= /bin/sh,\ sha224:d7910e1967342b4605cb73a550944044c631cd3514001900966962ac /bin/ls,\
sha256:hOtoe/iK6SlGg7w4BfZBBdSsXjUmTJ5+ts51yjh7vkM=,\
sha256:1IXHRCxXgSnIEnb+xBz4PAfWaPdXIBWKFF0QCwxJ5G4= /bin/sh,\
sha512:srzYEQ2aqzm+it3f74opTMkIImZRLxBARVpb0g9RSouJYdLt7DTRMEY4Ry9NyaOiDoUIplpNjqYH0JMYPVdFnw\ sha512:srzYEQ2aqzm+it3f74opTMkIImZRLxBARVpb0g9RSouJYdLt7DTRMEY4Ry9NyaOiDoUIplpNjqYH0JMYPVdFnw\
/bin/kill /bin/kill

View File

@@ -1,6 +1,6 @@
Parses OK. Parses OK.
Cmnd_Alias LS = sha224:d06a2617c98d377c250edd470fd5e576327748d82915d6e33b5f8db1 /bin/ls Cmnd_Alias LS = sha224:d06a2617c98d377c250edd470fd5e576327748d82915d6e33b5f8db1, sha224:d7910e1967342b4605cb73a550944044c631cd3514001900966962ac /bin/ls
Cmnd_Alias SH = sha256:hOtoe/iK6SlGg7w4BfZBBdSsXjUmTJ5+ts51yjh7vkM= /bin/sh Cmnd_Alias SH = sha256:hOtoe/iK6SlGg7w4BfZBBdSsXjUmTJ5+ts51yjh7vkM=, sha256:1IXHRCxXgSnIEnb+xBz4PAfWaPdXIBWKFF0QCwxJ5G4= /bin/sh
millert ALL = LS, SH, sha512:srzYEQ2aqzm+it3f74opTMkIImZRLxBARVpb0g9RSouJYdLt7DTRMEY4Ry9NyaOiDoUIplpNjqYH0JMYPVdFnw /bin/kill millert ALL = LS, SH, sha512:srzYEQ2aqzm+it3f74opTMkIImZRLxBARVpb0g9RSouJYdLt7DTRMEY4Ry9NyaOiDoUIplpNjqYH0JMYPVdFnw /bin/kill

View File

@@ -1,4 +1,4 @@
CMNDALIAS ALIAS = SHA224_TOK : DIGEST COMMAND CMNDALIAS ALIAS = SHA224_TOK : DIGEST , SHA224_TOK : DIGEST COMMAND
CMNDALIAS ALIAS = SHA256_TOK : DIGEST COMMAND CMNDALIAS ALIAS = SHA256_TOK : DIGEST , SHA256_TOK : DIGEST COMMAND
WORD(5) ALL = ALIAS , ALIAS , SHA512_TOK : DIGEST COMMAND WORD(5) ALL = ALIAS , ALIAS , SHA512_TOK : DIGEST COMMAND

View File

@@ -27,6 +27,5 @@ bool sudo_ldap_is_negated(char **valp);
bool sudo_ldap_add_default(const char *var, const char *val, int op, char *source, struct defaults_list *defs); bool sudo_ldap_add_default(const char *var, const char *val, int op, char *source, struct defaults_list *defs);
int sudo_ldap_parse_option(char *optstr, char **varp, char **valp); int sudo_ldap_parse_option(char *optstr, char **varp, char **valp);
struct privilege *sudo_ldap_role_to_priv(const char *cn, void *hosts, void *runasusers, void *runasgroups, void *cmnds, void *opts, const char *notbefore, const char *notafter, bool warnings, bool store_options, sudo_ldap_iter_t iter); struct privilege *sudo_ldap_role_to_priv(const char *cn, void *hosts, void *runasusers, void *runasgroups, void *cmnds, void *opts, const char *notbefore, const char *notafter, bool warnings, bool store_options, sudo_ldap_iter_t iter);
struct command_digest *sudo_ldap_extract_digest(char **cmnd, struct command_digest *digest);
#endif /* SUDOERS_LDAP_H */ #endif /* SUDOERS_LDAP_H */