summaryrefslogtreecommitdiffstats
path: root/debian
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--debian/changelog16
-rw-r--r--debian/patches/0016-CVE-2023-7090-Fix-special-handling-of-ipa_hostname-t.patch2574
-rw-r--r--debian/patches/0017-Escape-control-characters-in-log-messages-and-sudore.patch663
-rw-r--r--debian/patches/0018-Add-missing-separator-between-environment-variables-.patch24
-rw-r--r--debian/patches/series4
-rwxr-xr-xdebian/rules12
-rw-r--r--debian/salsa-ci.yml (renamed from debian/.gitlab-ci.yml)0
-rwxr-xr-xdebian/tests/01-getroot100
-rwxr-xr-xdebian/tests/02-1003969-audit-no-resolve43
-rwxr-xr-xdebian/tests/03-getroot-ldap132
-rw-r--r--debian/tests/03/ldif/container.ldif5
-rw-r--r--debian/tests/03/ldif/debconf16
-rw-r--r--debian/tests/03/ldif/sudoers.ldif32
-rwxr-xr-xdebian/tests/04-getroot-sssd138
-rw-r--r--debian/tests/04/ldif/adminpw-example-com.ldif4
-rw-r--r--debian/tests/04/ldif/adminpw.ldif7
-rw-r--r--debian/tests/04/ldif/container.ldif5
-rw-r--r--debian/tests/04/ldif/debconf15
-rw-r--r--debian/tests/04/ldif/ldap.conf6
-rw-r--r--debian/tests/04/ldif/ldapsudoers1
-rw-r--r--debian/tests/04/ldif/ldapsudoers.ldif6
-rw-r--r--debian/tests/04/ldif/server_cert.pem30
-rw-r--r--debian/tests/04/ldif/server_key.pem52
-rw-r--r--debian/tests/04/ldif/slapd-default7
-rw-r--r--debian/tests/04/ldif/sss-ous.ldif9
-rwxr-xr-xdebian/tests/04/ldif/sssd.conf24
-rw-r--r--debian/tests/04/ldif/testuser1.ldif16
-rw-r--r--debian/tests/04/ldif/testuser2.ldif17
-rw-r--r--debian/tests/04/ldif/tls.ldif10
-rwxr-xr-xdebian/tests/common/asuser7
-rw-r--r--debian/tests/control16
31 files changed, 3985 insertions, 6 deletions
diff --git a/debian/changelog b/debian/changelog
index 53cca7a..3b94c40 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,19 @@
+sudo (1.8.27-1+deb10u6) buster-security; urgency=high
+
+ * Non-maintainer upload by the LTS Security Team.
+ * Fix CVE-2023-7090: A flaw was found in sudo in the handling of
+ ipa_hostname, where ipa_hostname from /etc/sssd/sssd.conf
+ was not propagated in sudo. Therefore, it leads to
+ privilege mismanagement vulnerability in applications,
+ where client hosts retain privileges even after retracting them.
+ * Fix CVE-2023-28486: Sudo did not escape control characters
+ in log messages.
+ * Fix CVE-2023-28487: Sudo did not escape control characters
+ in sudoreplay output.
+ * Regenerate parsers from yacc file.
+
+ -- Bastien Roucariès <rouca@debian.org> Sun, 21 Jan 2024 20:52:36 +0000
+
sudo (1.8.27-1+deb10u5progress5u1) engywuck-security; urgency=high
* Uploading to engywuck-security, remaining changes:
diff --git a/debian/patches/0016-CVE-2023-7090-Fix-special-handling-of-ipa_hostname-t.patch b/debian/patches/0016-CVE-2023-7090-Fix-special-handling-of-ipa_hostname-t.patch
new file mode 100644
index 0000000..49c5b3d
--- /dev/null
+++ b/debian/patches/0016-CVE-2023-7090-Fix-special-handling-of-ipa_hostname-t.patch
@@ -0,0 +1,2574 @@
+From: "Todd C. Miller" <Todd.Miller@sudo.ws>
+Date: Thu, 15 Aug 2019 14:20:12 -0600
+Subject: CVE-2023-7090: Fix special handling of ipa_hostname that was lost
+
+We now include the long and short hostname in sudo parser container.
+
+[backport: remove generated yacc file]
+
+origin: https://www.sudo.ws/repos/sudo/rev/b4f31dbe3109
+bug-debian-security: https://security-tracker.debian.org/tracker/CVE-2023-7090
+---
+ plugins/sudoers/file.c | 2 +-
+ plugins/sudoers/gram.c | 2300 -----------------------------------------------
+ plugins/sudoers/gram.h | 63 --
+ plugins/sudoers/gram.y | 9 +-
+ plugins/sudoers/ldap.c | 2 +-
+ plugins/sudoers/match.c | 23 +-
+ plugins/sudoers/parse.h | 3 +-
+ plugins/sudoers/sssd.c | 7 +-
+ 8 files changed, 30 insertions(+), 2379 deletions(-)
+ delete mode 100644 plugins/sudoers/gram.c
+ delete mode 100644 plugins/sudoers/gram.h
+
+diff --git a/plugins/sudoers/file.c b/plugins/sudoers/file.c
+index a8b02b3..7ea9522 100644
+--- a/plugins/sudoers/file.c
++++ b/plugins/sudoers/file.c
+@@ -83,7 +83,7 @@ sudo_file_open(struct sudo_nss *nss)
+ if (handle != NULL) {
+ handle->fp = open_sudoers(sudoers_file, false, NULL);
+ if (handle->fp != NULL) {
+- init_parse_tree(&handle->parse_tree);
++ init_parse_tree(&handle->parse_tree, NULL, NULL);
+ } else {
+ free(handle);
+ handle = NULL;
+diff --git a/plugins/sudoers/gram.c b/plugins/sudoers/gram.c
+deleted file mode 100644
+index fff971b..0000000
+--- a/plugins/sudoers/gram.c
++++ /dev/null
+@@ -1,2300 +0,0 @@
+-/*
+- * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+- * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+- */
+-
+-#include <config.h>
+-#include <stdlib.h>
+-#include <string.h>
+-#define YYBYACC 1
+-#define YYMAJOR 1
+-#define YYMINOR 9
+-#define YYLEX yylex()
+-#define YYEMPTY -1
+-#define yyclearin (yychar=(YYEMPTY))
+-#define yyerrok (yyerrflag=0)
+-#define YYRECOVERING() (yyerrflag!=0)
+-#define yyparse sudoersparse
+-#define yylex sudoerslex
+-#define yyerror sudoerserror
+-#define yychar sudoerschar
+-#define yyval sudoersval
+-#define yylval sudoerslval
+-#define yydebug sudoersdebug
+-#define yynerrs sudoersnerrs
+-#define yyerrflag sudoerserrflag
+-#define yyss sudoersss
+-#define yysslim sudoerssslim
+-#define yyssp sudoersssp
+-#define yyvs sudoersvs
+-#define yyvsp sudoersvsp
+-#define yystacksize sudoersstacksize
+-#define yylhs sudoerslhs
+-#define yylen sudoerslen
+-#define yydefred sudoersdefred
+-#define yydgoto sudoersdgoto
+-#define yysindex sudoerssindex
+-#define yyrindex sudoersrindex
+-#define yygindex sudoersgindex
+-#define yytable sudoerstable
+-#define yycheck sudoerscheck
+-#define yyname sudoersname
+-#define yyrule sudoersrule
+-#define YYPREFIX "sudoers"
+-#line 2 "gram.y"
+-/*
+- * Copyright (c) 1996, 1998-2005, 2007-2013, 2014-2018
+- * Todd C. Miller <Todd.Miller@sudo.ws>
+- *
+- * Permission to use, copy, modify, and distribute this software for any
+- * purpose with or without fee is hereby granted, provided that the above
+- * copyright notice and this permission notice appear in all copies.
+- *
+- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+- *
+- * Sponsored in part by the Defense Advanced Research Projects
+- * Agency (DARPA) and Air Force Research Laboratory, Air Force
+- * Materiel Command, USAF, under agreement number F39502-99-1-0512.
+- */
+-
+-#include <config.h>
+-
+-#include <sys/types.h>
+-#include <stdio.h>
+-#include <stdlib.h>
+-#include <stddef.h>
+-#ifdef HAVE_STRING_H
+-# include <string.h>
+-#endif /* HAVE_STRING_H */
+-#ifdef HAVE_STRINGS_H
+-# include <strings.h>
+-#endif /* HAVE_STRINGS_H */
+-#include <unistd.h>
+-#if defined(HAVE_STDINT_H)
+-# include <stdint.h>
+-#elif defined(HAVE_INTTYPES_H)
+-# include <inttypes.h>
+-#endif
+-#if defined(YYBISON) && defined(HAVE_ALLOCA_H) && !defined(__GNUC__)
+-# include <alloca.h>
+-#endif /* YYBISON && HAVE_ALLOCA_H && !__GNUC__ */
+-#include <errno.h>
+-
+-#include "sudoers.h"
+-#include "sudo_digest.h"
+-#include "toke.h"
+-
+-/* If we last saw a newline the entry is on the preceding line. */
+-#define this_lineno (last_token == COMMENT ? sudolineno - 1 : sudolineno)
+-
+-/*
+- * Globals
+- */
+-bool sudoers_warnings = true;
+-bool parse_error = false;
+-int errorlineno = -1;
+-char *errorfile = NULL;
+-
+-struct sudoers_parse_tree parsed_policy = {
+- TAILQ_HEAD_INITIALIZER(parsed_policy.userspecs),
+- TAILQ_HEAD_INITIALIZER(parsed_policy.defaults),
+- NULL /* aliases */
+-};
+-
+-/*
+- * Local protoypes
+- */
+-static void init_options(struct command_options *opts);
+-static bool add_defaults(int, struct member *, struct defaults *);
+-static bool add_userspec(struct member *, struct privilege *);
+-static struct defaults *new_default(char *, char *, short);
+-static struct member *new_member(char *, int);
+-static struct command_digest *new_digest(int, char *);
+-#line 78 "gram.y"
+-#ifndef YYSTYPE_DEFINED
+-#define YYSTYPE_DEFINED
+-typedef union {
+- struct cmndspec *cmndspec;
+- struct defaults *defaults;
+- struct member *member;
+- struct runascontainer *runas;
+- struct privilege *privilege;
+- struct command_digest *digest;
+- struct sudo_command command;
+- struct command_options options;
+- struct cmndtag tag;
+- char *string;
+- int tok;
+-} YYSTYPE;
+-#endif /* YYSTYPE_DEFINED */
+-#line 131 "gram.c"
+-#define COMMAND 257
+-#define ALIAS 258
+-#define DEFVAR 259
+-#define NTWKADDR 260
+-#define NETGROUP 261
+-#define USERGROUP 262
+-#define WORD 263
+-#define DIGEST 264
+-#define DEFAULTS 265
+-#define DEFAULTS_HOST 266
+-#define DEFAULTS_USER 267
+-#define DEFAULTS_RUNAS 268
+-#define DEFAULTS_CMND 269
+-#define NOPASSWD 270
+-#define PASSWD 271
+-#define NOEXEC 272
+-#define EXEC 273
+-#define SETENV 274
+-#define NOSETENV 275
+-#define LOG_INPUT 276
+-#define NOLOG_INPUT 277
+-#define LOG_OUTPUT 278
+-#define NOLOG_OUTPUT 279
+-#define MAIL 280
+-#define NOMAIL 281
+-#define FOLLOW 282
+-#define NOFOLLOW 283
+-#define ALL 284
+-#define COMMENT 285
+-#define HOSTALIAS 286
+-#define CMNDALIAS 287
+-#define USERALIAS 288
+-#define RUNASALIAS 289
+-#define ERROR 290
+-#define TYPE 291
+-#define ROLE 292
+-#define PRIVS 293
+-#define LIMITPRIVS 294
+-#define CMND_TIMEOUT 295
+-#define NOTBEFORE 296
+-#define NOTAFTER 297
+-#define MYSELF 298
+-#define SHA224_TOK 299
+-#define SHA256_TOK 300
+-#define SHA384_TOK 301
+-#define SHA512_TOK 302
+-#define YYERRCODE 256
+-#if defined(__cplusplus) || defined(__STDC__)
+-const short sudoerslhs[] =
+-#else
+-short sudoerslhs[] =
+-#endif
+- { -1,
+- 0, 0, 32, 32, 33, 33, 33, 33, 33, 33,
+- 33, 33, 33, 33, 33, 33, 4, 4, 3, 3,
+- 3, 3, 3, 21, 21, 20, 11, 11, 9, 9,
+- 9, 9, 9, 2, 2, 1, 31, 31, 31, 31,
+- 7, 7, 6, 6, 28, 29, 30, 24, 25, 26,
+- 27, 18, 18, 19, 19, 19, 19, 19, 23, 23,
+- 23, 23, 23, 23, 23, 23, 22, 22, 22, 22,
+- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+- 22, 5, 5, 5, 35, 35, 38, 10, 10, 36,
+- 36, 39, 8, 8, 37, 37, 40, 34, 34, 41,
+- 14, 14, 12, 12, 13, 13, 13, 13, 13, 17,
+- 17, 15, 15, 16, 16, 16,
+-};
+-#if defined(__cplusplus) || defined(__STDC__)
+-const short sudoerslen[] =
+-#else
+-short sudoerslen[] =
+-#endif
+- { 2,
+- 0, 1, 1, 2, 1, 2, 2, 2, 2, 2,
+- 2, 2, 3, 3, 3, 3, 1, 3, 1, 2,
+- 3, 3, 3, 1, 3, 3, 1, 2, 1, 1,
+- 1, 1, 1, 1, 3, 4, 3, 3, 3, 3,
+- 1, 2, 1, 2, 3, 3, 3, 3, 3, 3,
+- 3, 0, 3, 0, 1, 3, 2, 1, 0, 2,
+- 2, 2, 2, 2, 2, 2, 0, 2, 2, 2,
+- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+- 2, 1, 1, 1, 1, 3, 3, 1, 3, 1,
+- 3, 3, 1, 3, 1, 3, 3, 1, 3, 3,
+- 1, 3, 1, 2, 1, 1, 1, 1, 1, 1,
+- 3, 1, 2, 1, 1, 1,
+-};
+-#if defined(__cplusplus) || defined(__STDC__)
+-const short sudoersdefred[] =
+-#else
+-short sudoersdefred[] =
+-#endif
+- { 0,
+- 0, 105, 107, 108, 109, 0, 0, 0, 0, 0,
+- 106, 5, 0, 0, 0, 0, 0, 0, 101, 103,
+- 0, 0, 3, 6, 0, 0, 17, 0, 29, 32,
+- 31, 33, 30, 0, 27, 0, 88, 0, 0, 84,
+- 83, 82, 0, 0, 0, 0, 0, 43, 41, 93,
+- 0, 0, 0, 0, 85, 0, 0, 90, 0, 0,
+- 98, 0, 0, 95, 104, 0, 0, 24, 0, 4,
+- 0, 0, 0, 20, 0, 28, 0, 0, 0, 0,
+- 44, 0, 0, 0, 0, 0, 0, 42, 0, 0,
+- 0, 0, 0, 0, 0, 0, 102, 0, 0, 21,
+- 22, 23, 18, 89, 37, 38, 39, 40, 94, 0,
+- 86, 0, 91, 0, 99, 0, 96, 0, 34, 0,
+- 59, 25, 0, 0, 0, 0, 0, 114, 116, 115,
+- 0, 110, 112, 0, 0, 53, 35, 0, 0, 0,
+- 0, 0, 0, 0, 0, 63, 64, 65, 66, 62,
+- 60, 61, 113, 0, 0, 0, 0, 0, 0, 0,
+- 0, 0, 68, 69, 70, 71, 72, 73, 74, 75,
+- 76, 77, 80, 81, 78, 79, 36, 111, 49, 48,
+- 50, 51, 45, 46, 47,
+-};
+-#if defined(__cplusplus) || defined(__STDC__)
+-const short sudoersdgoto[] =
+-#else
+-short sudoersdgoto[] =
+-#endif
+- { 18,
+- 119, 120, 27, 28, 48, 49, 50, 51, 35, 67,
+- 37, 19, 20, 21, 132, 133, 134, 121, 125, 68,
+- 69, 145, 127, 146, 147, 148, 149, 150, 151, 152,
+- 52, 22, 23, 60, 54, 57, 63, 55, 58, 64,
+- 61,
+-};
+-#if defined(__cplusplus) || defined(__STDC__)
+-const short sudoerssindex[] =
+-#else
+-short sudoerssindex[] =
+-#endif
+- { 512,
+- -272, 0, 0, 0, 0, -23, 227, -19, -19, -5,
+- 0, 0, -239, -236, -234, -232, -231, 0, 0, 0,
+- -33, 512, 0, 0, -3, -220, 0, 3, 0, 0,
+- 0, 0, 0, -225, 0, -28, 0, -24, -24, 0,
+- 0, 0, -240, -15, -8, 2, 4, 0, 0, 0,
+- -21, -12, -9, 6, 0, 7, 12, 0, 10, 14,
+- 0, 13, 25, 0, 0, -19, -36, 0, 26, 0,
+- -208, -202, -198, 0, -23, 0, 227, 3, 3, 3,
+- 0, -179, -178, -174, -173, -5, 3, 0, 227, -239,
+- -5, -236, -19, -234, -19, -232, 0, 52, 227, 0,
+- 0, 0, 0, 0, 0, 0, 0, 0, 0, 50,
+- 0, 51, 0, 54, 0, 54, 0, -29, 0, 55,
+- 0, 0, 289, -7, 59, 52, -216, 0, 0, 0,
+- -217, 0, 0, 57, 289, 0, 0, 32, 41, 42,
+- 43, 44, 45, 47, 450, 0, 0, 0, 0, 0,
+- 0, 0, 0, 289, 57, -154, -153, -150, -149, -148,
+- -147, -146, 0, 0, 0, 0, 0, 0, 0, 0,
+- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+- 0, 0, 0, 0, 0,};
+-#if defined(__cplusplus) || defined(__STDC__)
+-const short sudoersrindex[] =
+-#else
+-short sudoersrindex[] =
+-#endif
+- { 118,
+- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+- 0, 119, 0, 0, 1, 0, 0, 145, 0, 0,
+- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+- 0, 0, 0, 159, 0, 0, 193, 0, 0, 207,
+- 0, 0, 241, 0, 0, 0, 0, 0, 275, 0,
+- 0, 0, 0, 0, 0, 0, 0, 309, 323, 357,
+- 0, 0, 0, 0, 0, 0, 371, 0, 0, 0,
+- 0, 0, 0, 0, 0, 0, 0, 404, 0, 0,
+- 0, 0, 0, 0, 0, 0, 0, 0, 0, 15,
+- 0, 49, 0, 63, 0, 97, 0, 79, 0, 111,
+- 0, 0, 81, 82, 0, 404, 483, 0, 0, 0,
+- 0, 0, 0, 83, 0, 0, 0, 0, 0, 0,
+- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+- 0, 0, 0, 0, 84, 0, 0, 0, 0, 0,
+- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+- 0, 0, 0, 0, 0,};
+-#if defined(__cplusplus) || defined(__STDC__)
+-const short sudoersgindex[] =
+-#else
+-short sudoersgindex[] =
+-#endif
+- { 0,
+- 5, 0, 53, 18, 86, 74, -79, 36, 98, -1,
+- 56, 68, 120, -6, -18, 8, 11, 0, 0, 39,
+- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+- 0, 0, 113, 0, 0, 0, 0, 58, 48, 46,
+- 60,
+-};
+-#define YYTABLESIZE 801
+-#if defined(__cplusplus) || defined(__STDC__)
+-const short sudoerstable[] =
+-#else
+-short sudoerstable[] =
+-#endif
+- { 34,
+- 19, 38, 39, 17, 26, 36, 109, 77, 26, 26,
+- 66, 26, 24, 17, 87, 77, 40, 41, 53, 66,
+- 43, 56, 86, 59, 98, 62, 2, 43, 123, 3,
+- 4, 5, 29, 19, 30, 31, 66, 32, 74, 72,
+- 128, 73, 82, 42, 19, 129, 75, 87, 92, 83,
+- 135, 89, 11, 78, 100, 79, 80, 71, 33, 84,
+- 101, 85, 100, 90, 102, 177, 130, 91, 87, 92,
+- 93, 94, 87, 95, 138, 139, 140, 141, 142, 143,
+- 144, 92, 96, 99, 105, 106, 114, 110, 116, 107,
+- 108, 118, 156, 77, 86, 100, 97, 66, 126, 136,
+- 154, 157, 158, 159, 160, 161, 92, 162, 179, 180,
+- 26, 124, 181, 182, 183, 184, 185, 1, 2, 54,
+- 100, 58, 55, 57, 56, 88, 112, 103, 81, 97,
+- 137, 76, 104, 97, 70, 178, 65, 122, 153, 113,
+- 0, 117, 0, 26, 12, 155, 0, 111, 0, 0,
+- 0, 0, 0, 115, 97, 0, 0, 0, 9, 0,
+- 0, 0, 0, 0, 0, 0, 0, 0, 26, 0,
+- 0, 0, 0, 0, 0, 0, 0, 12, 0, 0,
+- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+- 0, 9, 10, 0, 0, 0, 0, 0, 0, 0,
+- 0, 0, 0, 0, 0, 0, 8, 0, 0, 0,
+- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+- 0, 0, 0, 0, 29, 10, 30, 31, 2, 32,
+- 25, 3, 4, 5, 25, 25, 0, 25, 2, 8,
+- 11, 3, 4, 5, 40, 41, 0, 0, 0, 0,
+- 33, 40, 41, 0, 11, 0, 19, 0, 19, 34,
+- 0, 19, 19, 19, 11, 19, 19, 19, 19, 19,
+- 87, 42, 87, 11, 7, 87, 87, 87, 42, 87,
+- 87, 87, 87, 87, 19, 19, 19, 19, 19, 19,
+- 0, 0, 0, 44, 45, 46, 47, 0, 87, 87,
+- 87, 87, 87, 87, 92, 0, 92, 7, 15, 92,
+- 92, 92, 0, 92, 92, 92, 92, 92, 100, 0,
+- 100, 131, 13, 100, 100, 100, 0, 100, 100, 100,
+- 100, 100, 92, 92, 92, 92, 92, 92, 0, 0,
+- 0, 15, 0, 0, 0, 0, 100, 100, 100, 100,
+- 100, 100, 97, 0, 97, 13, 14, 97, 97, 97,
+- 0, 97, 97, 97, 97, 97, 26, 0, 26, 0,
+- 16, 26, 26, 26, 0, 26, 26, 26, 26, 26,
+- 97, 97, 97, 97, 97, 97, 0, 0, 0, 14,
+- 0, 0, 0, 0, 26, 26, 26, 26, 26, 26,
+- 12, 0, 12, 16, 0, 12, 12, 12, 0, 12,
+- 12, 12, 12, 12, 9, 0, 9, 0, 0, 9,
+- 9, 9, 0, 9, 9, 9, 9, 9, 12, 12,
+- 12, 12, 12, 12, 0, 0, 52, 0, 0, 0,
+- 0, 0, 9, 9, 9, 9, 9, 9, 10, 0,
+- 10, 0, 0, 10, 10, 10, 0, 10, 10, 10,
+- 10, 10, 8, 0, 8, 0, 0, 8, 8, 8,
+- 0, 8, 8, 8, 8, 8, 10, 10, 10, 10,
+- 10, 10, 43, 0, 29, 0, 30, 31, 0, 32,
+- 8, 8, 8, 8, 8, 8, 11, 0, 11, 0,
+- 0, 11, 11, 11, 0, 11, 11, 11, 11, 11,
+- 33, 0, 0, 0, 0, 67, 0, 0, 0, 0,
+- 0, 0, 0, 0, 11, 11, 11, 11, 11, 11,
+- 7, 0, 7, 0, 0, 7, 7, 7, 0, 7,
+- 7, 7, 7, 7, 17, 0, 128, 0, 0, 0,
+- 0, 129, 0, 0, 0, 0, 0, 0, 7, 7,
+- 7, 7, 7, 7, 15, 0, 15, 0, 0, 15,
+- 15, 15, 130, 15, 15, 15, 15, 15, 13, 0,
+- 13, 0, 0, 13, 13, 13, 0, 13, 13, 13,
+- 13, 13, 15, 15, 15, 15, 15, 15, 0, 0,
+- 0, 0, 0, 0, 0, 0, 13, 13, 13, 13,
+- 13, 13, 14, 0, 14, 0, 0, 14, 14, 14,
+- 0, 14, 14, 14, 14, 14, 16, 0, 16, 0,
+- 0, 16, 16, 16, 0, 16, 16, 16, 16, 16,
+- 14, 14, 14, 14, 14, 14, 0, 0, 0, 0,
+- 0, 0, 0, 0, 16, 16, 16, 16, 16, 16,
+- 52, 52, 0, 0, 0, 0, 0, 0, 0, 0,
+- 0, 0, 0, 52, 52, 52, 52, 52, 52, 52,
+- 52, 52, 52, 52, 52, 52, 52, 52, 0, 0,
+- 0, 0, 0, 0, 52, 52, 52, 52, 52, 52,
+- 52, 0, 52, 52, 52, 52, 40, 41, 0, 0,
+- 0, 0, 0, 0, 0, 0, 0, 0, 0, 163,
+- 164, 165, 166, 167, 168, 169, 170, 171, 172, 173,
+- 174, 175, 176, 42, 0, 0, 0, 0, 0, 67,
+- 67, 0, 0, 0, 0, 0, 0, 0, 44, 45,
+- 46, 47, 67, 67, 67, 67, 67, 67, 67, 67,
+- 67, 67, 67, 67, 67, 67, 67, 1, 0, 2,
+- 0, 0, 3, 4, 5, 0, 6, 7, 8, 9,
+- 10, 67, 67, 67, 67, 0, 0, 0, 0, 0,
+- 0, 0, 0, 0, 0, 11, 12, 13, 14, 15,
+- 16,
+-};
+-#if defined(__cplusplus) || defined(__STDC__)
+-const short sudoerscheck[] =
+-#else
+-short sudoerscheck[] =
+-#endif
+- { 33,
+- 0, 8, 9, 33, 33, 7, 86, 44, 33, 33,
+- 44, 33, 285, 33, 0, 44, 257, 258, 258, 44,
+- 33, 258, 44, 258, 61, 258, 258, 33, 58, 261,
+- 262, 263, 258, 33, 260, 261, 44, 263, 259, 43,
+- 258, 45, 58, 284, 44, 263, 44, 33, 0, 58,
+- 58, 61, 284, 36, 263, 38, 39, 61, 284, 58,
+- 263, 58, 0, 58, 263, 145, 284, 61, 51, 58,
+- 61, 58, 58, 61, 291, 292, 293, 294, 295, 296,
+- 297, 33, 58, 58, 264, 264, 93, 89, 95, 264,
+- 264, 40, 61, 44, 44, 33, 0, 44, 44, 41,
+- 44, 61, 61, 61, 61, 61, 58, 61, 263, 263,
+- 0, 118, 263, 263, 263, 263, 263, 0, 0, 41,
+- 58, 41, 41, 41, 41, 52, 91, 75, 43, 33,
+- 126, 34, 77, 66, 22, 154, 17, 99, 131, 92,
+- -1, 96, -1, 33, 0, 135, -1, 90, -1, -1,
+- -1, -1, -1, 94, 58, -1, -1, -1, 0, -1,
+- -1, -1, -1, -1, -1, -1, -1, -1, 58, -1,
+- -1, -1, -1, -1, -1, -1, -1, 33, -1, -1,
+- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+- -1, 33, 0, -1, -1, -1, -1, -1, -1, -1,
+- -1, -1, -1, -1, -1, -1, 0, -1, -1, -1,
+- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+- -1, -1, -1, -1, 258, 33, 260, 261, 258, 263,
+- 259, 261, 262, 263, 259, 259, -1, 259, 258, 33,
+- 0, 261, 262, 263, 257, 258, -1, -1, -1, -1,
+- 284, 257, 258, -1, 284, -1, 256, -1, 258, 33,
+- -1, 261, 262, 263, 284, 265, 266, 267, 268, 269,
+- 256, 284, 258, 33, 0, 261, 262, 263, 284, 265,
+- 266, 267, 268, 269, 284, 285, 286, 287, 288, 289,
+- -1, -1, -1, 299, 300, 301, 302, -1, 284, 285,
+- 286, 287, 288, 289, 256, -1, 258, 33, 0, 261,
+- 262, 263, -1, 265, 266, 267, 268, 269, 256, -1,
+- 258, 33, 0, 261, 262, 263, -1, 265, 266, 267,
+- 268, 269, 284, 285, 286, 287, 288, 289, -1, -1,
+- -1, 33, -1, -1, -1, -1, 284, 285, 286, 287,
+- 288, 289, 256, -1, 258, 33, 0, 261, 262, 263,
+- -1, 265, 266, 267, 268, 269, 256, -1, 258, -1,
+- 0, 261, 262, 263, -1, 265, 266, 267, 268, 269,
+- 284, 285, 286, 287, 288, 289, -1, -1, -1, 33,
+- -1, -1, -1, -1, 284, 285, 286, 287, 288, 289,
+- 256, -1, 258, 33, -1, 261, 262, 263, -1, 265,
+- 266, 267, 268, 269, 256, -1, 258, -1, -1, 261,
+- 262, 263, -1, 265, 266, 267, 268, 269, 284, 285,
+- 286, 287, 288, 289, -1, -1, 33, -1, -1, -1,
+- -1, -1, 284, 285, 286, 287, 288, 289, 256, -1,
+- 258, -1, -1, 261, 262, 263, -1, 265, 266, 267,
+- 268, 269, 256, -1, 258, -1, -1, 261, 262, 263,
+- -1, 265, 266, 267, 268, 269, 284, 285, 286, 287,
+- 288, 289, 33, -1, 258, -1, 260, 261, -1, 263,
+- 284, 285, 286, 287, 288, 289, 256, -1, 258, -1,
+- -1, 261, 262, 263, -1, 265, 266, 267, 268, 269,
+- 284, -1, -1, -1, -1, 33, -1, -1, -1, -1,
+- -1, -1, -1, -1, 284, 285, 286, 287, 288, 289,
+- 256, -1, 258, -1, -1, 261, 262, 263, -1, 265,
+- 266, 267, 268, 269, 33, -1, 258, -1, -1, -1,
+- -1, 263, -1, -1, -1, -1, -1, -1, 284, 285,
+- 286, 287, 288, 289, 256, -1, 258, -1, -1, 261,
+- 262, 263, 284, 265, 266, 267, 268, 269, 256, -1,
+- 258, -1, -1, 261, 262, 263, -1, 265, 266, 267,
+- 268, 269, 284, 285, 286, 287, 288, 289, -1, -1,
+- -1, -1, -1, -1, -1, -1, 284, 285, 286, 287,
+- 288, 289, 256, -1, 258, -1, -1, 261, 262, 263,
+- -1, 265, 266, 267, 268, 269, 256, -1, 258, -1,
+- -1, 261, 262, 263, -1, 265, 266, 267, 268, 269,
+- 284, 285, 286, 287, 288, 289, -1, -1, -1, -1,
+- -1, -1, -1, -1, 284, 285, 286, 287, 288, 289,
+- 257, 258, -1, -1, -1, -1, -1, -1, -1, -1,
+- -1, -1, -1, 270, 271, 272, 273, 274, 275, 276,
+- 277, 278, 279, 280, 281, 282, 283, 284, -1, -1,
+- -1, -1, -1, -1, 291, 292, 293, 294, 295, 296,
+- 297, -1, 299, 300, 301, 302, 257, 258, -1, -1,
+- -1, -1, -1, -1, -1, -1, -1, -1, -1, 270,
+- 271, 272, 273, 274, 275, 276, 277, 278, 279, 280,
+- 281, 282, 283, 284, -1, -1, -1, -1, -1, 257,
+- 258, -1, -1, -1, -1, -1, -1, -1, 299, 300,
+- 301, 302, 270, 271, 272, 273, 274, 275, 276, 277,
+- 278, 279, 280, 281, 282, 283, 284, 256, -1, 258,
+- -1, -1, 261, 262, 263, -1, 265, 266, 267, 268,
+- 269, 299, 300, 301, 302, -1, -1, -1, -1, -1,
+- -1, -1, -1, -1, -1, 284, 285, 286, 287, 288,
+- 289,
+-};
+-#define YYFINAL 18
+-#ifndef YYDEBUG
+-#define YYDEBUG 0
+-#endif
+-#define YYMAXTOKEN 302
+-#if YYDEBUG
+-#if defined(__cplusplus) || defined(__STDC__)
+-const char * const sudoersname[] =
+-#else
+-char *sudoersname[] =
+-#endif
+- {
+-"end-of-file",0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+-"'!'",0,0,0,0,0,0,"'('","')'",0,"'+'","','","'-'",0,0,0,0,0,0,0,0,0,0,0,0,"':'",
+-0,0,"'='",0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+-"COMMAND","ALIAS","DEFVAR","NTWKADDR","NETGROUP","USERGROUP","WORD","DIGEST",
+-"DEFAULTS","DEFAULTS_HOST","DEFAULTS_USER","DEFAULTS_RUNAS","DEFAULTS_CMND",
+-"NOPASSWD","PASSWD","NOEXEC","EXEC","SETENV","NOSETENV","LOG_INPUT",
+-"NOLOG_INPUT","LOG_OUTPUT","NOLOG_OUTPUT","MAIL","NOMAIL","FOLLOW","NOFOLLOW",
+-"ALL","COMMENT","HOSTALIAS","CMNDALIAS","USERALIAS","RUNASALIAS","ERROR","TYPE",
+-"ROLE","PRIVS","LIMITPRIVS","CMND_TIMEOUT","NOTBEFORE","NOTAFTER","MYSELF",
+-"SHA224_TOK","SHA256_TOK","SHA384_TOK","SHA512_TOK",
+-};
+-#if defined(__cplusplus) || defined(__STDC__)
+-const char * const sudoersrule[] =
+-#else
+-char *sudoersrule[] =
+-#endif
+- {"$accept : file",
+-"file :",
+-"file : line",
+-"line : entry",
+-"line : line entry",
+-"entry : COMMENT",
+-"entry : error COMMENT",
+-"entry : userlist privileges",
+-"entry : USERALIAS useraliases",
+-"entry : HOSTALIAS hostaliases",
+-"entry : CMNDALIAS cmndaliases",
+-"entry : RUNASALIAS runasaliases",
+-"entry : DEFAULTS defaults_list",
+-"entry : DEFAULTS_USER userlist defaults_list",
+-"entry : DEFAULTS_RUNAS userlist defaults_list",
+-"entry : DEFAULTS_HOST hostlist defaults_list",
+-"entry : DEFAULTS_CMND cmndlist defaults_list",
+-"defaults_list : defaults_entry",
+-"defaults_list : defaults_list ',' defaults_entry",
+-"defaults_entry : DEFVAR",
+-"defaults_entry : '!' DEFVAR",
+-"defaults_entry : DEFVAR '=' WORD",
+-"defaults_entry : DEFVAR '+' WORD",
+-"defaults_entry : DEFVAR '-' WORD",
+-"privileges : privilege",
+-"privileges : privileges ':' privilege",
+-"privilege : hostlist '=' cmndspeclist",
+-"ophost : host",
+-"ophost : '!' host",
+-"host : ALIAS",
+-"host : ALL",
+-"host : NETGROUP",
+-"host : NTWKADDR",
+-"host : WORD",
+-"cmndspeclist : cmndspec",
+-"cmndspeclist : cmndspeclist ',' cmndspec",
+-"cmndspec : runasspec options cmndtag digcmnd",
+-"digest : SHA224_TOK ':' DIGEST",
+-"digest : SHA256_TOK ':' DIGEST",
+-"digest : SHA384_TOK ':' DIGEST",
+-"digest : SHA512_TOK ':' DIGEST",
+-"digcmnd : opcmnd",
+-"digcmnd : digest opcmnd",
+-"opcmnd : cmnd",
+-"opcmnd : '!' cmnd",
+-"timeoutspec : CMND_TIMEOUT '=' WORD",
+-"notbeforespec : NOTBEFORE '=' WORD",
+-"notafterspec : NOTAFTER '=' WORD",
+-"rolespec : ROLE '=' WORD",
+-"typespec : TYPE '=' WORD",
+-"privsspec : PRIVS '=' WORD",
+-"limitprivsspec : LIMITPRIVS '=' WORD",
+-"runasspec :",
+-"runasspec : '(' runaslist ')'",
+-"runaslist :",
+-"runaslist : userlist",
+-"runaslist : userlist ':' grouplist",
+-"runaslist : ':' grouplist",
+-"runaslist : ':'",
+-"options :",
+-"options : options notbeforespec",
+-"options : options notafterspec",
+-"options : options timeoutspec",
+-"options : options rolespec",
+-"options : options typespec",
+-"options : options privsspec",
+-"options : options limitprivsspec",
+-"cmndtag :",
+-"cmndtag : cmndtag NOPASSWD",
+-"cmndtag : cmndtag PASSWD",
+-"cmndtag : cmndtag NOEXEC",
+-"cmndtag : cmndtag EXEC",
+-"cmndtag : cmndtag SETENV",
+-"cmndtag : cmndtag NOSETENV",
+-"cmndtag : cmndtag LOG_INPUT",
+-"cmndtag : cmndtag NOLOG_INPUT",
+-"cmndtag : cmndtag LOG_OUTPUT",
+-"cmndtag : cmndtag NOLOG_OUTPUT",
+-"cmndtag : cmndtag FOLLOW",
+-"cmndtag : cmndtag NOFOLLOW",
+-"cmndtag : cmndtag MAIL",
+-"cmndtag : cmndtag NOMAIL",
+-"cmnd : ALL",
+-"cmnd : ALIAS",
+-"cmnd : COMMAND",
+-"hostaliases : hostalias",
+-"hostaliases : hostaliases ':' hostalias",
+-"hostalias : ALIAS '=' hostlist",
+-"hostlist : ophost",
+-"hostlist : hostlist ',' ophost",
+-"cmndaliases : cmndalias",
+-"cmndaliases : cmndaliases ':' cmndalias",
+-"cmndalias : ALIAS '=' cmndlist",
+-"cmndlist : digcmnd",
+-"cmndlist : cmndlist ',' digcmnd",
+-"runasaliases : runasalias",
+-"runasaliases : runasaliases ':' runasalias",
+-"runasalias : ALIAS '=' userlist",
+-"useraliases : useralias",
+-"useraliases : useraliases ':' useralias",
+-"useralias : ALIAS '=' userlist",
+-"userlist : opuser",
+-"userlist : userlist ',' opuser",
+-"opuser : user",
+-"opuser : '!' user",
+-"user : ALIAS",
+-"user : ALL",
+-"user : NETGROUP",
+-"user : USERGROUP",
+-"user : WORD",
+-"grouplist : opgroup",
+-"grouplist : grouplist ',' opgroup",
+-"opgroup : group",
+-"opgroup : '!' group",
+-"group : ALIAS",
+-"group : ALL",
+-"group : WORD",
+-};
+-#endif
+-#ifdef YYSTACKSIZE
+-#undef YYMAXDEPTH
+-#define YYMAXDEPTH YYSTACKSIZE
+-#else
+-#ifdef YYMAXDEPTH
+-#define YYSTACKSIZE YYMAXDEPTH
+-#else
+-#define YYSTACKSIZE 10000
+-#define YYMAXDEPTH 10000
+-#endif
+-#endif
+-#define YYINITSTACKSIZE 200
+-/* LINTUSED */
+-int yydebug;
+-int yynerrs;
+-int yyerrflag;
+-int yychar;
+-short *yyssp;
+-YYSTYPE *yyvsp;
+-YYSTYPE yyval;
+-YYSTYPE yylval;
+-short *yyss;
+-short *yysslim;
+-YYSTYPE *yyvs;
+-unsigned int yystacksize;
+-int yyparse(void);
+-#line 904 "gram.y"
+-void
+-sudoerserror(const char *s)
+-{
+- debug_decl(sudoerserror, SUDOERS_DEBUG_PARSER)
+-
+- /* Save the line the first error occurred on. */
+- if (errorlineno == -1) {
+- errorlineno = this_lineno;
+- rcstr_delref(errorfile);
+- errorfile = rcstr_addref(sudoers);
+- }
+- if (sudoers_warnings && s != NULL) {
+- LEXTRACE("<*> ");
+-#ifndef TRACELEXER
+- if (trace_print == NULL || trace_print == sudoers_trace_print) {
+- const char fmt[] = ">>> %s: %s near line %d <<<\n";
+- int oldlocale;
+-
+- /* Warnings are displayed in the user's locale. */
+- sudoers_setlocale(SUDOERS_LOCALE_USER, &oldlocale);
+- sudo_printf(SUDO_CONV_ERROR_MSG, _(fmt), sudoers, _(s), this_lineno);
+- sudoers_setlocale(oldlocale, NULL);
+- }
+-#endif
+- }
+- parse_error = true;
+- debug_return;
+-}
+-
+-static struct defaults *
+-new_default(char *var, char *val, short op)
+-{
+- struct defaults *d;
+- debug_decl(new_default, SUDOERS_DEBUG_PARSER)
+-
+- if ((d = calloc(1, sizeof(struct defaults))) == NULL) {
+- sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+- "unable to allocate memory");
+- debug_return_ptr(NULL);
+- }
+-
+- d->var = var;
+- d->val = val;
+- /* d->type = 0; */
+- d->op = op;
+- /* d->binding = NULL */
+- d->lineno = this_lineno;
+- d->file = rcstr_addref(sudoers);
+- HLTQ_INIT(d, entries);
+-
+- debug_return_ptr(d);
+-}
+-
+-static struct member *
+-new_member(char *name, int type)
+-{
+- struct member *m;
+- debug_decl(new_member, SUDOERS_DEBUG_PARSER)
+-
+- if ((m = calloc(1, sizeof(struct member))) == NULL) {
+- sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+- "unable to allocate memory");
+- debug_return_ptr(NULL);
+- }
+-
+- m->name = name;
+- m->type = type;
+- HLTQ_INIT(m, entries);
+-
+- debug_return_ptr(m);
+-}
+-
+-static struct command_digest *
+-new_digest(int digest_type, char *digest_str)
+-{
+- struct command_digest *digest;
+- debug_decl(new_digest, SUDOERS_DEBUG_PARSER)
+-
+- if ((digest = malloc(sizeof(*digest))) == NULL) {
+- sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+- "unable to allocate memory");
+- debug_return_ptr(NULL);
+- }
+-
+- digest->digest_type = digest_type;
+- digest->digest_str = digest_str;
+- if (digest->digest_str == NULL) {
+- sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+- "unable to allocate memory");
+- free(digest);
+- digest = NULL;
+- }
+-
+- debug_return_ptr(digest);
+-}
+-
+-/*
+- * Add a list of defaults structures to the defaults list.
+- * The binding, if non-NULL, specifies a list of hosts, users, or
+- * runas users the entries apply to (specified by the type).
+- */
+-static bool
+-add_defaults(int type, struct member *bmem, struct defaults *defs)
+-{
+- struct defaults *d, *next;
+- struct member_list *binding;
+- bool ret = true;
+- debug_decl(add_defaults, SUDOERS_DEBUG_PARSER)
+-
+- if (defs != NULL) {
+- /*
+- * We use a single binding for each entry in defs.
+- */
+- if ((binding = malloc(sizeof(*binding))) == NULL) {
+- sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+- "unable to allocate memory");
+- sudoerserror(N_("unable to allocate memory"));
+- debug_return_bool(false);
+- }
+- if (bmem != NULL)
+- HLTQ_TO_TAILQ(binding, bmem, entries);
+- else
+- TAILQ_INIT(binding);
+-
+- /*
+- * Set type and binding (who it applies to) for new entries.
+- * Then add to the global defaults list.
+- */
+- HLTQ_FOREACH_SAFE(d, defs, entries, next) {
+- d->type = type;
+- d->binding = binding;
+- TAILQ_INSERT_TAIL(&parsed_policy.defaults, d, entries);
+- }
+- }
+-
+- debug_return_bool(ret);
+-}
+-
+-/*
+- * Allocate a new struct userspec, populate it, and insert it at the
+- * end of the userspecs list.
+- */
+-static bool
+-add_userspec(struct member *members, struct privilege *privs)
+-{
+- struct userspec *u;
+- debug_decl(add_userspec, SUDOERS_DEBUG_PARSER)
+-
+- if ((u = calloc(1, sizeof(*u))) == NULL) {
+- sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+- "unable to allocate memory");
+- debug_return_bool(false);
+- }
+- u->lineno = this_lineno;
+- 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(&parsed_policy.userspecs, u, entries);
+-
+- debug_return_bool(true);
+-}
+-
+-/*
+- * Free a member struct and its contents.
+- */
+-void
+-free_member(struct member *m)
+-{
+- debug_decl(free_member, SUDOERS_DEBUG_PARSER)
+-
+- if (m->type == COMMAND) {
+- struct sudo_command *c = (struct sudo_command *)m->name;
+- free(c->cmnd);
+- free(c->args);
+- if (c->digest != NULL) {
+- free(c->digest->digest_str);
+- free(c->digest);
+- }
+- }
+- free(m->name);
+- free(m);
+-
+- debug_return;
+-}
+-
+-/*
+- * Free a tailq of members but not the struct member_list container itself.
+- */
+-void
+-free_members(struct member_list *members)
+-{
+- struct member *m;
+- debug_decl(free_members, SUDOERS_DEBUG_PARSER)
+-
+- while ((m = TAILQ_FIRST(members)) != NULL) {
+- TAILQ_REMOVE(members, m, entries);
+- free_member(m);
+- }
+-
+- debug_return;
+-}
+-
+-void
+-free_defaults(struct defaults_list *defs)
+-{
+- struct member_list *prev_binding = NULL;
+- struct defaults *def;
+- debug_decl(free_defaults, SUDOERS_DEBUG_PARSER)
+-
+- while ((def = TAILQ_FIRST(defs)) != NULL) {
+- TAILQ_REMOVE(defs, def, entries);
+- free_default(def, &prev_binding);
+- }
+-
+- debug_return;
+-}
+-
+-void
+-free_default(struct defaults *def, struct member_list **binding)
+-{
+- debug_decl(free_default, SUDOERS_DEBUG_PARSER)
+-
+- if (def->binding != *binding) {
+- *binding = def->binding;
+- if (def->binding != NULL) {
+- free_members(def->binding);
+- free(def->binding);
+- }
+- }
+- rcstr_delref(def->file);
+- free(def->var);
+- free(def->val);
+- free(def);
+-
+- debug_return;
+-}
+-
+-void
+-free_privilege(struct privilege *priv)
+-{
+- struct member_list *runasuserlist = NULL, *runasgrouplist = NULL;
+- struct member_list *prev_binding = NULL;
+- struct cmndspec *cs;
+- struct defaults *def;
+-#ifdef HAVE_SELINUX
+- char *role = NULL, *type = NULL;
+-#endif /* HAVE_SELINUX */
+-#ifdef HAVE_PRIV_SET
+- char *privs = NULL, *limitprivs = NULL;
+-#endif /* HAVE_PRIV_SET */
+- debug_decl(free_privilege, SUDOERS_DEBUG_PARSER)
+-
+- free(priv->ldap_role);
+- free_members(&priv->hostlist);
+- while ((cs = TAILQ_FIRST(&priv->cmndlist)) != NULL) {
+- TAILQ_REMOVE(&priv->cmndlist, cs, entries);
+-#ifdef HAVE_SELINUX
+- /* Only free the first instance of a role/type. */
+- if (cs->role != role) {
+- role = cs->role;
+- free(cs->role);
+- }
+- if (cs->type != type) {
+- type = cs->type;
+- free(cs->type);
+- }
+-#endif /* HAVE_SELINUX */
+-#ifdef HAVE_PRIV_SET
+- /* Only free the first instance of privs/limitprivs. */
+- if (cs->privs != privs) {
+- privs = cs->privs;
+- free(cs->privs);
+- }
+- if (cs->limitprivs != limitprivs) {
+- limitprivs = cs->limitprivs;
+- free(cs->limitprivs);
+- }
+-#endif /* HAVE_PRIV_SET */
+- /* Only free the first instance of runas user/group lists. */
+- if (cs->runasuserlist && cs->runasuserlist != runasuserlist) {
+- runasuserlist = cs->runasuserlist;
+- free_members(runasuserlist);
+- free(runasuserlist);
+- }
+- if (cs->runasgrouplist && cs->runasgrouplist != runasgrouplist) {
+- runasgrouplist = cs->runasgrouplist;
+- free_members(runasgrouplist);
+- free(runasgrouplist);
+- }
+- free_member(cs->cmnd);
+- free(cs);
+- }
+- while ((def = TAILQ_FIRST(&priv->defaults)) != NULL) {
+- TAILQ_REMOVE(&priv->defaults, def, entries);
+- free_default(def, &prev_binding);
+- }
+- free(priv);
+-
+- debug_return;
+-}
+-
+-void
+-free_userspecs(struct userspec_list *usl)
+-{
+- struct userspec *us;
+- debug_decl(free_userspecs, SUDOERS_DEBUG_PARSER)
+-
+- while ((us = TAILQ_FIRST(usl)) != NULL) {
+- TAILQ_REMOVE(usl, us, entries);
+- free_userspec(us);
+- }
+-
+- debug_return;
+-}
+-
+-void
+-free_userspec(struct userspec *us)
+-{
+- struct privilege *priv;
+- struct sudoers_comment *comment;
+- debug_decl(free_userspec, SUDOERS_DEBUG_PARSER)
+-
+- 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);
+-
+- debug_return;
+-}
+-
+-/*
+- * Initialized a sudoers parse tree.
+- */
+-void
+-init_parse_tree(struct sudoers_parse_tree *parse_tree)
+-{
+- TAILQ_INIT(&parse_tree->userspecs);
+- TAILQ_INIT(&parse_tree->defaults);
+- parse_tree->aliases = NULL;
+-}
+-
+-/*
+- * Move the contents of parsed_policy to new_tree.
+- */
+-void
+-reparent_parse_tree(struct sudoers_parse_tree *new_tree)
+-{
+- TAILQ_CONCAT(&new_tree->userspecs, &parsed_policy.userspecs, entries);
+- TAILQ_CONCAT(&new_tree->defaults, &parsed_policy.defaults, entries);
+- new_tree->aliases = parsed_policy.aliases;
+- parsed_policy.aliases = NULL;
+-}
+-
+-/*
+- * Free the contents of a sudoers parse tree and initialize it.
+- */
+-void
+-free_parse_tree(struct sudoers_parse_tree *parse_tree)
+-{
+- free_userspecs(&parse_tree->userspecs);
+- free_defaults(&parse_tree->defaults);
+- free_aliases(parse_tree->aliases);
+- parse_tree->aliases = NULL;
+-}
+-
+-/*
+- * Free up space used by data structures from a previous parser run and sets
+- * the current sudoers file to path.
+- */
+-bool
+-init_parser(const char *path, bool quiet)
+-{
+- bool ret = true;
+- debug_decl(init_parser, SUDOERS_DEBUG_PARSER)
+-
+- free_parse_tree(&parsed_policy);
+- init_lexer();
+-
+- rcstr_delref(sudoers);
+- if (path != NULL) {
+- if ((sudoers = rcstr_dup(path)) == NULL) {
+- sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+- ret = false;
+- }
+- } else {
+- sudoers = NULL;
+- }
+-
+- parse_error = false;
+- errorlineno = -1;
+- rcstr_delref(errorfile);
+- errorfile = NULL;
+- sudoers_warnings = !quiet;
+-
+- debug_return_bool(ret);
+-}
+-
+-/*
+- * Initialize all options in a cmndspec.
+- */
+-static void
+-init_options(struct command_options *opts)
+-{
+- opts->notbefore = UNSPEC;
+- opts->notafter = UNSPEC;
+- opts->timeout = UNSPEC;
+-#ifdef HAVE_SELINUX
+- opts->role = NULL;
+- opts->type = NULL;
+-#endif
+-#ifdef HAVE_PRIV_SET
+- opts->privs = NULL;
+- opts->limitprivs = NULL;
+-#endif
+-}
+-#line 1044 "gram.c"
+-/* allocate initial stack or double stack size, up to YYMAXDEPTH */
+-#if defined(__cplusplus) || defined(__STDC__)
+-static int yygrowstack(void)
+-#else
+-static int yygrowstack()
+-#endif
+-{
+- unsigned int newsize;
+- long sslen;
+- short *newss;
+- YYSTYPE *newvs;
+-
+- if ((newsize = yystacksize) == 0)
+- newsize = YYINITSTACKSIZE;
+- else if (newsize >= YYMAXDEPTH)
+- return -1;
+- else if ((newsize *= 2) > YYMAXDEPTH)
+- newsize = YYMAXDEPTH;
+-#ifdef SIZE_MAX
+-#define YY_SIZE_MAX SIZE_MAX
+-#else
+-#ifdef __STDC__
+-#define YY_SIZE_MAX 0xffffffffU
+-#else
+-#define YY_SIZE_MAX (unsigned int)0xffffffff
+-#endif
+-#endif
+- if (YY_SIZE_MAX / newsize < sizeof *newss)
+- goto bail;
+- sslen = yyssp - yyss;
+- newss = yyss ? (short *)realloc(yyss, newsize * sizeof *newss) :
+- (short *)malloc(newsize * sizeof *newss); /* overflow check above */
+- if (newss == NULL)
+- goto bail;
+- yyss = newss;
+- yyssp = newss + sslen;
+- newvs = yyvs ? (YYSTYPE *)realloc(yyvs, newsize * sizeof *newvs) :
+- (YYSTYPE *)malloc(newsize * sizeof *newvs); /* overflow check above */
+- if (newvs == NULL)
+- goto bail;
+- yyvs = newvs;
+- yyvsp = newvs + sslen;
+- yystacksize = newsize;
+- yysslim = yyss + newsize - 1;
+- return 0;
+-bail:
+- if (yyss)
+- free(yyss);
+- if (yyvs)
+- free(yyvs);
+- yyss = yyssp = NULL;
+- yyvs = yyvsp = NULL;
+- yystacksize = 0;
+- return -1;
+-}
+-
+-#define YYABORT goto yyabort
+-#define YYREJECT goto yyabort
+-#define YYACCEPT goto yyaccept
+-#define YYERROR goto yyerrlab
+-int
+-#if defined(__cplusplus) || defined(__STDC__)
+-yyparse(void)
+-#else
+-yyparse()
+-#endif
+-{
+- int yym, yyn, yystate;
+-#if YYDEBUG
+-#if defined(__cplusplus) || defined(__STDC__)
+- const char *yys;
+-#else /* !(defined(__cplusplus) || defined(__STDC__)) */
+- char *yys;
+-#endif /* !(defined(__cplusplus) || defined(__STDC__)) */
+-
+- if ((yys = getenv("YYDEBUG")))
+- {
+- yyn = *yys;
+- if (yyn >= '0' && yyn <= '9')
+- yydebug = yyn - '0';
+- }
+-#endif /* YYDEBUG */
+-
+- yynerrs = 0;
+- yyerrflag = 0;
+- yychar = (-1);
+-
+- if (yyss == NULL && yygrowstack()) goto yyoverflow;
+- yyssp = yyss;
+- yyvsp = yyvs;
+- *yyssp = yystate = 0;
+-
+-yyloop:
+- if ((yyn = yydefred[yystate]) != 0) goto yyreduce;
+- if (yychar < 0)
+- {
+- if ((yychar = yylex()) < 0) yychar = 0;
+-#if YYDEBUG
+- if (yydebug)
+- {
+- yys = 0;
+- if (yychar <= YYMAXTOKEN) yys = yyname[yychar];
+- if (!yys) yys = "illegal-symbol";
+- printf("%sdebug: state %d, reading %d (%s)\n",
+- YYPREFIX, yystate, yychar, yys);
+- }
+-#endif
+- }
+- if ((yyn = yysindex[yystate]) && (yyn += yychar) >= 0 &&
+- yyn <= YYTABLESIZE && yycheck[yyn] == yychar)
+- {
+-#if YYDEBUG
+- if (yydebug)
+- printf("%sdebug: state %d, shifting to state %d\n",
+- YYPREFIX, yystate, yytable[yyn]);
+-#endif
+- if (yyssp >= yysslim && yygrowstack())
+- {
+- goto yyoverflow;
+- }
+- *++yyssp = yystate = yytable[yyn];
+- *++yyvsp = yylval;
+- yychar = (-1);
+- if (yyerrflag > 0) --yyerrflag;
+- goto yyloop;
+- }
+- if ((yyn = yyrindex[yystate]) && (yyn += yychar) >= 0 &&
+- yyn <= YYTABLESIZE && yycheck[yyn] == yychar)
+- {
+- yyn = yytable[yyn];
+- goto yyreduce;
+- }
+- if (yyerrflag) goto yyinrecovery;
+-#if defined(__GNUC__)
+- goto yynewerror;
+-#endif
+-yynewerror:
+- yyerror("syntax error");
+-#if defined(__GNUC__)
+- goto yyerrlab;
+-#endif
+-yyerrlab:
+- ++yynerrs;
+-yyinrecovery:
+- if (yyerrflag < 3)
+- {
+- yyerrflag = 3;
+- for (;;)
+- {
+- if ((yyn = yysindex[*yyssp]) && (yyn += YYERRCODE) >= 0 &&
+- yyn <= YYTABLESIZE && yycheck[yyn] == YYERRCODE)
+- {
+-#if YYDEBUG
+- if (yydebug)
+- printf("%sdebug: state %d, error recovery shifting\
+- to state %d\n", YYPREFIX, *yyssp, yytable[yyn]);
+-#endif
+- if (yyssp >= yysslim && yygrowstack())
+- {
+- goto yyoverflow;
+- }
+- *++yyssp = yystate = yytable[yyn];
+- *++yyvsp = yylval;
+- goto yyloop;
+- }
+- else
+- {
+-#if YYDEBUG
+- if (yydebug)
+- printf("%sdebug: error recovery discarding state %d\n",
+- YYPREFIX, *yyssp);
+-#endif
+- if (yyssp <= yyss) goto yyabort;
+- --yyssp;
+- --yyvsp;
+- }
+- }
+- }
+- else
+- {
+- if (yychar == 0) goto yyabort;
+-#if YYDEBUG
+- if (yydebug)
+- {
+- yys = 0;
+- if (yychar <= YYMAXTOKEN) yys = yyname[yychar];
+- if (!yys) yys = "illegal-symbol";
+- printf("%sdebug: state %d, error recovery discards token %d (%s)\n",
+- YYPREFIX, yystate, yychar, yys);
+- }
+-#endif
+- yychar = (-1);
+- goto yyloop;
+- }
+-yyreduce:
+-#if YYDEBUG
+- if (yydebug)
+- printf("%sdebug: state %d, reducing by rule %d (%s)\n",
+- YYPREFIX, yystate, yyn, yyrule[yyn]);
+-#endif
+- yym = yylen[yyn];
+- if (yym)
+- yyval = yyvsp[1-yym];
+- else
+- memset(&yyval, 0, sizeof yyval);
+- switch (yyn)
+- {
+-case 1:
+-#line 176 "gram.y"
+-{ ; }
+-break;
+-case 5:
+-#line 184 "gram.y"
+-{
+- ;
+- }
+-break;
+-case 6:
+-#line 187 "gram.y"
+-{
+- yyerrok;
+- }
+-break;
+-case 7:
+-#line 190 "gram.y"
+-{
+- if (!add_userspec(yyvsp[-1].member, yyvsp[0].privilege)) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- }
+-break;
+-case 8:
+-#line 196 "gram.y"
+-{
+- ;
+- }
+-break;
+-case 9:
+-#line 199 "gram.y"
+-{
+- ;
+- }
+-break;
+-case 10:
+-#line 202 "gram.y"
+-{
+- ;
+- }
+-break;
+-case 11:
+-#line 205 "gram.y"
+-{
+- ;
+- }
+-break;
+-case 12:
+-#line 208 "gram.y"
+-{
+- if (!add_defaults(DEFAULTS, NULL, yyvsp[0].defaults))
+- YYERROR;
+- }
+-break;
+-case 13:
+-#line 212 "gram.y"
+-{
+- if (!add_defaults(DEFAULTS_USER, yyvsp[-1].member, yyvsp[0].defaults))
+- YYERROR;
+- }
+-break;
+-case 14:
+-#line 216 "gram.y"
+-{
+- if (!add_defaults(DEFAULTS_RUNAS, yyvsp[-1].member, yyvsp[0].defaults))
+- YYERROR;
+- }
+-break;
+-case 15:
+-#line 220 "gram.y"
+-{
+- if (!add_defaults(DEFAULTS_HOST, yyvsp[-1].member, yyvsp[0].defaults))
+- YYERROR;
+- }
+-break;
+-case 16:
+-#line 224 "gram.y"
+-{
+- if (!add_defaults(DEFAULTS_CMND, yyvsp[-1].member, yyvsp[0].defaults))
+- YYERROR;
+- }
+-break;
+-case 18:
+-#line 231 "gram.y"
+-{
+- HLTQ_CONCAT(yyvsp[-2].defaults, yyvsp[0].defaults, entries);
+- yyval.defaults = yyvsp[-2].defaults;
+- }
+-break;
+-case 19:
+-#line 237 "gram.y"
+-{
+- yyval.defaults = new_default(yyvsp[0].string, NULL, true);
+- if (yyval.defaults == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- }
+-break;
+-case 20:
+-#line 244 "gram.y"
+-{
+- yyval.defaults = new_default(yyvsp[0].string, NULL, false);
+- if (yyval.defaults == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- }
+-break;
+-case 21:
+-#line 251 "gram.y"
+-{
+- yyval.defaults = new_default(yyvsp[-2].string, yyvsp[0].string, true);
+- if (yyval.defaults == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- }
+-break;
+-case 22:
+-#line 258 "gram.y"
+-{
+- yyval.defaults = new_default(yyvsp[-2].string, yyvsp[0].string, '+');
+- if (yyval.defaults == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- }
+-break;
+-case 23:
+-#line 265 "gram.y"
+-{
+- yyval.defaults = new_default(yyvsp[-2].string, yyvsp[0].string, '-');
+- if (yyval.defaults == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- }
+-break;
+-case 25:
+-#line 275 "gram.y"
+-{
+- HLTQ_CONCAT(yyvsp[-2].privilege, yyvsp[0].privilege, entries);
+- yyval.privilege = yyvsp[-2].privilege;
+- }
+-break;
+-case 26:
+-#line 281 "gram.y"
+-{
+- struct privilege *p = calloc(1, sizeof(*p));
+- if (p == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- TAILQ_INIT(&p->defaults);
+- HLTQ_TO_TAILQ(&p->hostlist, yyvsp[-2].member, entries);
+- HLTQ_TO_TAILQ(&p->cmndlist, yyvsp[0].cmndspec, entries);
+- HLTQ_INIT(p, entries);
+- yyval.privilege = p;
+- }
+-break;
+-case 27:
+-#line 295 "gram.y"
+-{
+- yyval.member = yyvsp[0].member;
+- yyval.member->negated = false;
+- }
+-break;
+-case 28:
+-#line 299 "gram.y"
+-{
+- yyval.member = yyvsp[0].member;
+- yyval.member->negated = true;
+- }
+-break;
+-case 29:
+-#line 305 "gram.y"
+-{
+- yyval.member = new_member(yyvsp[0].string, ALIAS);
+- if (yyval.member == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- }
+-break;
+-case 30:
+-#line 312 "gram.y"
+-{
+- yyval.member = new_member(NULL, ALL);
+- if (yyval.member == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- }
+-break;
+-case 31:
+-#line 319 "gram.y"
+-{
+- yyval.member = new_member(yyvsp[0].string, NETGROUP);
+- if (yyval.member == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- }
+-break;
+-case 32:
+-#line 326 "gram.y"
+-{
+- yyval.member = new_member(yyvsp[0].string, NTWKADDR);
+- if (yyval.member == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- }
+-break;
+-case 33:
+-#line 333 "gram.y"
+-{
+- yyval.member = new_member(yyvsp[0].string, WORD);
+- if (yyval.member == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- }
+-break;
+-case 35:
+-#line 343 "gram.y"
+-{
+- struct cmndspec *prev;
+- prev = HLTQ_LAST(yyvsp[-2].cmndspec, cmndspec, entries);
+- HLTQ_CONCAT(yyvsp[-2].cmndspec, yyvsp[0].cmndspec, entries);
+-#ifdef HAVE_SELINUX
+- /* propagate role and type */
+- if (yyvsp[0].cmndspec->role == NULL && yyvsp[0].cmndspec->type == NULL) {
+- yyvsp[0].cmndspec->role = prev->role;
+- yyvsp[0].cmndspec->type = prev->type;
+- }
+-#endif /* HAVE_SELINUX */
+-#ifdef HAVE_PRIV_SET
+- /* propagate privs & limitprivs */
+- if (yyvsp[0].cmndspec->privs == NULL && yyvsp[0].cmndspec->limitprivs == NULL) {
+- yyvsp[0].cmndspec->privs = prev->privs;
+- yyvsp[0].cmndspec->limitprivs = prev->limitprivs;
+- }
+-#endif /* HAVE_PRIV_SET */
+- /* propagate command time restrictions */
+- if (yyvsp[0].cmndspec->notbefore == UNSPEC)
+- yyvsp[0].cmndspec->notbefore = prev->notbefore;
+- if (yyvsp[0].cmndspec->notafter == UNSPEC)
+- yyvsp[0].cmndspec->notafter = prev->notafter;
+- /* propagate command timeout */
+- if (yyvsp[0].cmndspec->timeout == UNSPEC)
+- yyvsp[0].cmndspec->timeout = prev->timeout;
+- /* propagate tags and runas list */
+- if (yyvsp[0].cmndspec->tags.nopasswd == UNSPEC)
+- yyvsp[0].cmndspec->tags.nopasswd = prev->tags.nopasswd;
+- if (yyvsp[0].cmndspec->tags.noexec == UNSPEC)
+- yyvsp[0].cmndspec->tags.noexec = prev->tags.noexec;
+- if (yyvsp[0].cmndspec->tags.setenv == UNSPEC &&
+- prev->tags.setenv != IMPLIED)
+- yyvsp[0].cmndspec->tags.setenv = prev->tags.setenv;
+- if (yyvsp[0].cmndspec->tags.log_input == UNSPEC)
+- yyvsp[0].cmndspec->tags.log_input = prev->tags.log_input;
+- if (yyvsp[0].cmndspec->tags.log_output == UNSPEC)
+- yyvsp[0].cmndspec->tags.log_output = prev->tags.log_output;
+- if (yyvsp[0].cmndspec->tags.send_mail == UNSPEC)
+- yyvsp[0].cmndspec->tags.send_mail = prev->tags.send_mail;
+- if (yyvsp[0].cmndspec->tags.follow == UNSPEC)
+- yyvsp[0].cmndspec->tags.follow = prev->tags.follow;
+- if ((yyvsp[0].cmndspec->runasuserlist == NULL &&
+- yyvsp[0].cmndspec->runasgrouplist == NULL) &&
+- (prev->runasuserlist != NULL ||
+- prev->runasgrouplist != NULL)) {
+- yyvsp[0].cmndspec->runasuserlist = prev->runasuserlist;
+- yyvsp[0].cmndspec->runasgrouplist = prev->runasgrouplist;
+- }
+- yyval.cmndspec = yyvsp[-2].cmndspec;
+- }
+-break;
+-case 36:
+-#line 396 "gram.y"
+-{
+- struct cmndspec *cs = calloc(1, sizeof(*cs));
+- if (cs == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- if (yyvsp[-3].runas != NULL) {
+- if (yyvsp[-3].runas->runasusers != NULL) {
+- cs->runasuserlist =
+- malloc(sizeof(*cs->runasuserlist));
+- if (cs->runasuserlist == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- HLTQ_TO_TAILQ(cs->runasuserlist,
+- yyvsp[-3].runas->runasusers, entries);
+- }
+- if (yyvsp[-3].runas->runasgroups != NULL) {
+- cs->runasgrouplist =
+- malloc(sizeof(*cs->runasgrouplist));
+- if (cs->runasgrouplist == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- HLTQ_TO_TAILQ(cs->runasgrouplist,
+- yyvsp[-3].runas->runasgroups, entries);
+- }
+- free(yyvsp[-3].runas);
+- }
+-#ifdef HAVE_SELINUX
+- cs->role = yyvsp[-2].options.role;
+- cs->type = yyvsp[-2].options.type;
+-#endif
+-#ifdef HAVE_PRIV_SET
+- cs->privs = yyvsp[-2].options.privs;
+- cs->limitprivs = yyvsp[-2].options.limitprivs;
+-#endif
+- cs->notbefore = yyvsp[-2].options.notbefore;
+- cs->notafter = yyvsp[-2].options.notafter;
+- cs->timeout = yyvsp[-2].options.timeout;
+- cs->tags = yyvsp[-1].tag;
+- cs->cmnd = yyvsp[0].member;
+- HLTQ_INIT(cs, entries);
+- /* sudo "ALL" implies the SETENV tag */
+- if (cs->cmnd->type == ALL && !cs->cmnd->negated &&
+- cs->tags.setenv == UNSPEC)
+- cs->tags.setenv = IMPLIED;
+- yyval.cmndspec = cs;
+- }
+-break;
+-case 37:
+-#line 447 "gram.y"
+-{
+- yyval.digest = new_digest(SUDO_DIGEST_SHA224, yyvsp[0].string);
+- if (yyval.digest == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- }
+-break;
+-case 38:
+-#line 454 "gram.y"
+-{
+- yyval.digest = new_digest(SUDO_DIGEST_SHA256, yyvsp[0].string);
+- if (yyval.digest == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- }
+-break;
+-case 39:
+-#line 461 "gram.y"
+-{
+- yyval.digest = new_digest(SUDO_DIGEST_SHA384, yyvsp[0].string);
+- if (yyval.digest == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- }
+-break;
+-case 40:
+-#line 468 "gram.y"
+-{
+- yyval.digest = new_digest(SUDO_DIGEST_SHA512, yyvsp[0].string);
+- if (yyval.digest == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- }
+-break;
+-case 41:
+-#line 477 "gram.y"
+-{
+- yyval.member = yyvsp[0].member;
+- }
+-break;
+-case 42:
+-#line 480 "gram.y"
+-{
+- if (yyvsp[0].member->type != COMMAND) {
+- sudoerserror(N_("a digest requires a path name"));
+- YYERROR;
+- }
+- /* XXX - yuck */
+- ((struct sudo_command *) yyvsp[0].member->name)->digest = yyvsp[-1].digest;
+- yyval.member = yyvsp[0].member;
+- }
+-break;
+-case 43:
+-#line 491 "gram.y"
+-{
+- yyval.member = yyvsp[0].member;
+- yyval.member->negated = false;
+- }
+-break;
+-case 44:
+-#line 495 "gram.y"
+-{
+- yyval.member = yyvsp[0].member;
+- yyval.member->negated = true;
+- }
+-break;
+-case 45:
+-#line 501 "gram.y"
+-{
+- yyval.string = yyvsp[0].string;
+- }
+-break;
+-case 46:
+-#line 506 "gram.y"
+-{
+- yyval.string = yyvsp[0].string;
+- }
+-break;
+-case 47:
+-#line 510 "gram.y"
+-{
+- yyval.string = yyvsp[0].string;
+- }
+-break;
+-case 48:
+-#line 515 "gram.y"
+-{
+- yyval.string = yyvsp[0].string;
+- }
+-break;
+-case 49:
+-#line 520 "gram.y"
+-{
+- yyval.string = yyvsp[0].string;
+- }
+-break;
+-case 50:
+-#line 525 "gram.y"
+-{
+- yyval.string = yyvsp[0].string;
+- }
+-break;
+-case 51:
+-#line 529 "gram.y"
+-{
+- yyval.string = yyvsp[0].string;
+- }
+-break;
+-case 52:
+-#line 534 "gram.y"
+-{
+- yyval.runas = NULL;
+- }
+-break;
+-case 53:
+-#line 537 "gram.y"
+-{
+- yyval.runas = yyvsp[-1].runas;
+- }
+-break;
+-case 54:
+-#line 542 "gram.y"
+-{
+- yyval.runas = calloc(1, sizeof(struct runascontainer));
+- if (yyval.runas != NULL) {
+- yyval.runas->runasusers = new_member(NULL, MYSELF);
+- /* $$->runasgroups = NULL; */
+- if (yyval.runas->runasusers == NULL) {
+- free(yyval.runas);
+- yyval.runas = NULL;
+- }
+- }
+- if (yyval.runas == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- }
+-break;
+-case 55:
+-#line 557 "gram.y"
+-{
+- yyval.runas = calloc(1, sizeof(struct runascontainer));
+- if (yyval.runas == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- yyval.runas->runasusers = yyvsp[0].member;
+- /* $$->runasgroups = NULL; */
+- }
+-break;
+-case 56:
+-#line 566 "gram.y"
+-{
+- yyval.runas = calloc(1, sizeof(struct runascontainer));
+- if (yyval.runas == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- yyval.runas->runasusers = yyvsp[-2].member;
+- yyval.runas->runasgroups = yyvsp[0].member;
+- }
+-break;
+-case 57:
+-#line 575 "gram.y"
+-{
+- yyval.runas = calloc(1, sizeof(struct runascontainer));
+- if (yyval.runas == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- /* $$->runasusers = NULL; */
+- yyval.runas->runasgroups = yyvsp[0].member;
+- }
+-break;
+-case 58:
+-#line 584 "gram.y"
+-{
+- yyval.runas = calloc(1, sizeof(struct runascontainer));
+- if (yyval.runas != NULL) {
+- yyval.runas->runasusers = new_member(NULL, MYSELF);
+- /* $$->runasgroups = NULL; */
+- if (yyval.runas->runasusers == NULL) {
+- free(yyval.runas);
+- yyval.runas = NULL;
+- }
+- }
+- if (yyval.runas == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- }
+-break;
+-case 59:
+-#line 601 "gram.y"
+-{
+- init_options(&yyval.options);
+- }
+-break;
+-case 60:
+-#line 604 "gram.y"
+-{
+- yyval.options.notbefore = parse_gentime(yyvsp[0].string);
+- free(yyvsp[0].string);
+- if (yyval.options.notbefore == -1) {
+- sudoerserror(N_("invalid notbefore value"));
+- YYERROR;
+- }
+- }
+-break;
+-case 61:
+-#line 612 "gram.y"
+-{
+- yyval.options.notafter = parse_gentime(yyvsp[0].string);
+- free(yyvsp[0].string);
+- if (yyval.options.notafter == -1) {
+- sudoerserror(N_("invalid notafter value"));
+- YYERROR;
+- }
+- }
+-break;
+-case 62:
+-#line 620 "gram.y"
+-{
+- yyval.options.timeout = parse_timeout(yyvsp[0].string);
+- free(yyvsp[0].string);
+- if (yyval.options.timeout == -1) {
+- if (errno == ERANGE)
+- sudoerserror(N_("timeout value too large"));
+- else
+- sudoerserror(N_("invalid timeout value"));
+- YYERROR;
+- }
+- }
+-break;
+-case 63:
+-#line 631 "gram.y"
+-{
+-#ifdef HAVE_SELINUX
+- free(yyval.options.role);
+- yyval.options.role = yyvsp[0].string;
+-#endif
+- }
+-break;
+-case 64:
+-#line 637 "gram.y"
+-{
+-#ifdef HAVE_SELINUX
+- free(yyval.options.type);
+- yyval.options.type = yyvsp[0].string;
+-#endif
+- }
+-break;
+-case 65:
+-#line 643 "gram.y"
+-{
+-#ifdef HAVE_PRIV_SET
+- free(yyval.options.privs);
+- yyval.options.privs = yyvsp[0].string;
+-#endif
+- }
+-break;
+-case 66:
+-#line 649 "gram.y"
+-{
+-#ifdef HAVE_PRIV_SET
+- free(yyval.options.limitprivs);
+- yyval.options.limitprivs = yyvsp[0].string;
+-#endif
+- }
+-break;
+-case 67:
+-#line 657 "gram.y"
+-{
+- TAGS_INIT(yyval.tag);
+- }
+-break;
+-case 68:
+-#line 660 "gram.y"
+-{
+- yyval.tag.nopasswd = true;
+- }
+-break;
+-case 69:
+-#line 663 "gram.y"
+-{
+- yyval.tag.nopasswd = false;
+- }
+-break;
+-case 70:
+-#line 666 "gram.y"
+-{
+- yyval.tag.noexec = true;
+- }
+-break;
+-case 71:
+-#line 669 "gram.y"
+-{
+- yyval.tag.noexec = false;
+- }
+-break;
+-case 72:
+-#line 672 "gram.y"
+-{
+- yyval.tag.setenv = true;
+- }
+-break;
+-case 73:
+-#line 675 "gram.y"
+-{
+- yyval.tag.setenv = false;
+- }
+-break;
+-case 74:
+-#line 678 "gram.y"
+-{
+- yyval.tag.log_input = true;
+- }
+-break;
+-case 75:
+-#line 681 "gram.y"
+-{
+- yyval.tag.log_input = false;
+- }
+-break;
+-case 76:
+-#line 684 "gram.y"
+-{
+- yyval.tag.log_output = true;
+- }
+-break;
+-case 77:
+-#line 687 "gram.y"
+-{
+- yyval.tag.log_output = false;
+- }
+-break;
+-case 78:
+-#line 690 "gram.y"
+-{
+- yyval.tag.follow = true;
+- }
+-break;
+-case 79:
+-#line 693 "gram.y"
+-{
+- yyval.tag.follow = false;
+- }
+-break;
+-case 80:
+-#line 696 "gram.y"
+-{
+- yyval.tag.send_mail = true;
+- }
+-break;
+-case 81:
+-#line 699 "gram.y"
+-{
+- yyval.tag.send_mail = false;
+- }
+-break;
+-case 82:
+-#line 704 "gram.y"
+-{
+- yyval.member = new_member(NULL, ALL);
+- if (yyval.member == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- }
+-break;
+-case 83:
+-#line 711 "gram.y"
+-{
+- yyval.member = new_member(yyvsp[0].string, ALIAS);
+- if (yyval.member == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- }
+-break;
+-case 84:
+-#line 718 "gram.y"
+-{
+- struct sudo_command *c = calloc(1, sizeof(*c));
+- if (c == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- c->cmnd = yyvsp[0].command.cmnd;
+- c->args = yyvsp[0].command.args;
+- yyval.member = new_member((char *)c, COMMAND);
+- if (yyval.member == NULL) {
+- free(c);
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- }
+-break;
+-case 87:
+-#line 739 "gram.y"
+-{
+- const char *s;
+- s = alias_add(&parsed_policy, yyvsp[-2].string, HOSTALIAS,
+- sudoers, this_lineno, yyvsp[0].member);
+- if (s != NULL) {
+- sudoerserror(s);
+- YYERROR;
+- }
+- }
+-break;
+-case 89:
+-#line 751 "gram.y"
+-{
+- HLTQ_CONCAT(yyvsp[-2].member, yyvsp[0].member, entries);
+- yyval.member = yyvsp[-2].member;
+- }
+-break;
+-case 92:
+-#line 761 "gram.y"
+-{
+- const char *s;
+- s = alias_add(&parsed_policy, yyvsp[-2].string, CMNDALIAS,
+- sudoers, this_lineno, yyvsp[0].member);
+- if (s != NULL) {
+- sudoerserror(s);
+- YYERROR;
+- }
+- }
+-break;
+-case 94:
+-#line 773 "gram.y"
+-{
+- HLTQ_CONCAT(yyvsp[-2].member, yyvsp[0].member, entries);
+- yyval.member = yyvsp[-2].member;
+- }
+-break;
+-case 97:
+-#line 783 "gram.y"
+-{
+- const char *s;
+- s = alias_add(&parsed_policy, yyvsp[-2].string, RUNASALIAS,
+- sudoers, this_lineno, yyvsp[0].member);
+- if (s != NULL) {
+- sudoerserror(s);
+- YYERROR;
+- }
+- }
+-break;
+-case 100:
+-#line 798 "gram.y"
+-{
+- const char *s;
+- s = alias_add(&parsed_policy, yyvsp[-2].string, USERALIAS,
+- sudoers, this_lineno, yyvsp[0].member);
+- if (s != NULL) {
+- sudoerserror(s);
+- YYERROR;
+- }
+- }
+-break;
+-case 102:
+-#line 810 "gram.y"
+-{
+- HLTQ_CONCAT(yyvsp[-2].member, yyvsp[0].member, entries);
+- yyval.member = yyvsp[-2].member;
+- }
+-break;
+-case 103:
+-#line 816 "gram.y"
+-{
+- yyval.member = yyvsp[0].member;
+- yyval.member->negated = false;
+- }
+-break;
+-case 104:
+-#line 820 "gram.y"
+-{
+- yyval.member = yyvsp[0].member;
+- yyval.member->negated = true;
+- }
+-break;
+-case 105:
+-#line 826 "gram.y"
+-{
+- yyval.member = new_member(yyvsp[0].string, ALIAS);
+- if (yyval.member == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- }
+-break;
+-case 106:
+-#line 833 "gram.y"
+-{
+- yyval.member = new_member(NULL, ALL);
+- if (yyval.member == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- }
+-break;
+-case 107:
+-#line 840 "gram.y"
+-{
+- yyval.member = new_member(yyvsp[0].string, NETGROUP);
+- if (yyval.member == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- }
+-break;
+-case 108:
+-#line 847 "gram.y"
+-{
+- yyval.member = new_member(yyvsp[0].string, USERGROUP);
+- if (yyval.member == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- }
+-break;
+-case 109:
+-#line 854 "gram.y"
+-{
+- yyval.member = new_member(yyvsp[0].string, WORD);
+- if (yyval.member == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- }
+-break;
+-case 111:
+-#line 864 "gram.y"
+-{
+- HLTQ_CONCAT(yyvsp[-2].member, yyvsp[0].member, entries);
+- yyval.member = yyvsp[-2].member;
+- }
+-break;
+-case 112:
+-#line 870 "gram.y"
+-{
+- yyval.member = yyvsp[0].member;
+- yyval.member->negated = false;
+- }
+-break;
+-case 113:
+-#line 874 "gram.y"
+-{
+- yyval.member = yyvsp[0].member;
+- yyval.member->negated = true;
+- }
+-break;
+-case 114:
+-#line 880 "gram.y"
+-{
+- yyval.member = new_member(yyvsp[0].string, ALIAS);
+- if (yyval.member == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- }
+-break;
+-case 115:
+-#line 887 "gram.y"
+-{
+- yyval.member = new_member(NULL, ALL);
+- if (yyval.member == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- }
+-break;
+-case 116:
+-#line 894 "gram.y"
+-{
+- yyval.member = new_member(yyvsp[0].string, WORD);
+- if (yyval.member == NULL) {
+- sudoerserror(N_("unable to allocate memory"));
+- YYERROR;
+- }
+- }
+-break;
+-#line 2173 "gram.c"
+- }
+- yyssp -= yym;
+- yystate = *yyssp;
+- yyvsp -= yym;
+- yym = yylhs[yyn];
+- if (yystate == 0 && yym == 0)
+- {
+-#if YYDEBUG
+- if (yydebug)
+- printf("%sdebug: after reduction, shifting from state 0 to\
+- state %d\n", YYPREFIX, YYFINAL);
+-#endif
+- yystate = YYFINAL;
+- *++yyssp = YYFINAL;
+- *++yyvsp = yyval;
+- if (yychar < 0)
+- {
+- if ((yychar = yylex()) < 0) yychar = 0;
+-#if YYDEBUG
+- if (yydebug)
+- {
+- yys = 0;
+- if (yychar <= YYMAXTOKEN) yys = yyname[yychar];
+- if (!yys) yys = "illegal-symbol";
+- printf("%sdebug: state %d, reading %d (%s)\n",
+- YYPREFIX, YYFINAL, yychar, yys);
+- }
+-#endif
+- }
+- if (yychar == 0) goto yyaccept;
+- goto yyloop;
+- }
+- if ((yyn = yygindex[yym]) && (yyn += yystate) >= 0 &&
+- yyn <= YYTABLESIZE && yycheck[yyn] == yystate)
+- yystate = yytable[yyn];
+- else
+- yystate = yydgoto[yym];
+-#if YYDEBUG
+- if (yydebug)
+- printf("%sdebug: after reduction, shifting from state %d \
+-to state %d\n", YYPREFIX, *yyssp, yystate);
+-#endif
+- if (yyssp >= yysslim && yygrowstack())
+- {
+- goto yyoverflow;
+- }
+- *++yyssp = yystate;
+- *++yyvsp = yyval;
+- goto yyloop;
+-yyoverflow:
+- yyerror("yacc stack overflow");
+-yyabort:
+- if (yyss)
+- free(yyss);
+- if (yyvs)
+- free(yyvs);
+- yyss = yyssp = NULL;
+- yyvs = yyvsp = NULL;
+- yystacksize = 0;
+- return (1);
+-yyaccept:
+- if (yyss)
+- free(yyss);
+- if (yyvs)
+- free(yyvs);
+- yyss = yyssp = NULL;
+- yyvs = yyvsp = NULL;
+- yystacksize = 0;
+- return (0);
+-}
+diff --git a/plugins/sudoers/gram.h b/plugins/sudoers/gram.h
+deleted file mode 100644
+index c53f97a..0000000
+--- a/plugins/sudoers/gram.h
++++ /dev/null
+@@ -1,63 +0,0 @@
+-#define COMMAND 257
+-#define ALIAS 258
+-#define DEFVAR 259
+-#define NTWKADDR 260
+-#define NETGROUP 261
+-#define USERGROUP 262
+-#define WORD 263
+-#define DIGEST 264
+-#define DEFAULTS 265
+-#define DEFAULTS_HOST 266
+-#define DEFAULTS_USER 267
+-#define DEFAULTS_RUNAS 268
+-#define DEFAULTS_CMND 269
+-#define NOPASSWD 270
+-#define PASSWD 271
+-#define NOEXEC 272
+-#define EXEC 273
+-#define SETENV 274
+-#define NOSETENV 275
+-#define LOG_INPUT 276
+-#define NOLOG_INPUT 277
+-#define LOG_OUTPUT 278
+-#define NOLOG_OUTPUT 279
+-#define MAIL 280
+-#define NOMAIL 281
+-#define FOLLOW 282
+-#define NOFOLLOW 283
+-#define ALL 284
+-#define COMMENT 285
+-#define HOSTALIAS 286
+-#define CMNDALIAS 287
+-#define USERALIAS 288
+-#define RUNASALIAS 289
+-#define ERROR 290
+-#define TYPE 291
+-#define ROLE 292
+-#define PRIVS 293
+-#define LIMITPRIVS 294
+-#define CMND_TIMEOUT 295
+-#define NOTBEFORE 296
+-#define NOTAFTER 297
+-#define MYSELF 298
+-#define SHA224_TOK 299
+-#define SHA256_TOK 300
+-#define SHA384_TOK 301
+-#define SHA512_TOK 302
+-#ifndef YYSTYPE_DEFINED
+-#define YYSTYPE_DEFINED
+-typedef union {
+- struct cmndspec *cmndspec;
+- struct defaults *defaults;
+- struct member *member;
+- struct runascontainer *runas;
+- struct privilege *privilege;
+- struct command_digest *digest;
+- struct sudo_command command;
+- struct command_options options;
+- struct cmndtag tag;
+- char *string;
+- int tok;
+-} YYSTYPE;
+-#endif /* YYSTYPE_DEFINED */
+-extern YYSTYPE sudoerslval;
+diff --git a/plugins/sudoers/gram.y b/plugins/sudoers/gram.y
+index 0665ce9..6ad0f04 100644
+--- a/plugins/sudoers/gram.y
++++ b/plugins/sudoers/gram.y
+@@ -61,7 +61,9 @@ char *errorfile = NULL;
+ struct sudoers_parse_tree parsed_policy = {
+ TAILQ_HEAD_INITIALIZER(parsed_policy.userspecs),
+ TAILQ_HEAD_INITIALIZER(parsed_policy.defaults),
+- NULL /* aliases */
++ NULL, /* aliases */
++ NULL, /* lhost */
++ NULL /* shost */
+ };
+
+ /*
+@@ -1244,11 +1246,14 @@ free_userspec(struct userspec *us)
+ * Initialized a sudoers parse tree.
+ */
+ void
+-init_parse_tree(struct sudoers_parse_tree *parse_tree)
++init_parse_tree(struct sudoers_parse_tree *parse_tree, const char *lhost,
++ const char *shost)
+ {
+ TAILQ_INIT(&parse_tree->userspecs);
+ TAILQ_INIT(&parse_tree->defaults);
+ parse_tree->aliases = NULL;
++ parse_tree->shost = shost;
++ parse_tree->lhost = lhost;
+ }
+
+ /*
+diff --git a/plugins/sudoers/ldap.c b/plugins/sudoers/ldap.c
+index bc2baec..fe6cb3c 100644
+--- a/plugins/sudoers/ldap.c
++++ b/plugins/sudoers/ldap.c
+@@ -1663,7 +1663,7 @@ sudo_ldap_open(struct sudo_nss *nss)
+ }
+ handle->ld = ld;
+ /* handle->pw = NULL; */
+- init_parse_tree(&handle->parse_tree);
++ init_parse_tree(&handle->parse_tree, NULL, NULL);
+ nss->handle = handle;
+
+ done:
+diff --git a/plugins/sudoers/match.c b/plugins/sudoers/match.c
+index 7c8e9d8..444d654 100644
+--- a/plugins/sudoers/match.c
++++ b/plugins/sudoers/match.c
+@@ -99,8 +99,10 @@ int
+ user_matches(struct sudoers_parse_tree *parse_tree, const struct passwd *pw,
+ const struct member *m)
+ {
+- struct alias *a;
++ const char *lhost = parse_tree->lhost ? parse_tree->lhost : user_runhost;
++ const char *shost = parse_tree->shost ? parse_tree->shost : user_srunhost;
+ int matched = UNSPEC;
++ struct alias *a;
+ debug_decl(user_matches, SUDOERS_DEBUG_MATCH)
+
+ switch (m->type) {
+@@ -109,8 +111,8 @@ user_matches(struct sudoers_parse_tree *parse_tree, const struct passwd *pw,
+ break;
+ case NETGROUP:
+ if (netgr_matches(m->name,
+- def_netgroup_tuple ? user_runhost : NULL,
+- def_netgroup_tuple ? user_srunhost : NULL, pw->pw_name))
++ def_netgroup_tuple ? lhost : NULL,
++ def_netgroup_tuple ? shost : NULL, pw->pw_name))
+ matched = !m->negated;
+ break;
+ case USERGROUP:
+@@ -180,11 +182,13 @@ runaslist_matches(struct sudoers_parse_tree *parse_tree,
+ const struct member_list *user_list, const struct member_list *group_list,
+ struct member **matching_user, struct member **matching_group)
+ {
++ const char *lhost = parse_tree->lhost ? parse_tree->lhost : user_runhost;
++ const char *shost = parse_tree->shost ? parse_tree->shost : user_srunhost;
++ int user_matched = UNSPEC;
++ int group_matched = UNSPEC;
+ struct member *m;
+ struct alias *a;
+ int rc;
+- int user_matched = UNSPEC;
+- int group_matched = UNSPEC;
+ debug_decl(runaslist_matches, SUDOERS_DEBUG_MATCH)
+
+ if (ISSET(sudo_user.flags, RUNAS_USER_SPECIFIED) || !ISSET(sudo_user.flags, RUNAS_GROUP_SPECIFIED)) {
+@@ -202,8 +206,8 @@ runaslist_matches(struct sudoers_parse_tree *parse_tree,
+ break;
+ case NETGROUP:
+ if (netgr_matches(m->name,
+- def_netgroup_tuple ? user_runhost : NULL,
+- def_netgroup_tuple ? user_srunhost : NULL,
++ def_netgroup_tuple ? lhost : NULL,
++ def_netgroup_tuple ? shost : NULL,
+ runas_pw->pw_name))
+ user_matched = !m->negated;
+ break;
+@@ -336,7 +340,10 @@ int
+ hostlist_matches(struct sudoers_parse_tree *parse_tree, const struct passwd *pw,
+ const struct member_list *list)
+ {
+- return hostlist_matches_int(parse_tree, pw, user_runhost, user_srunhost, list);
++ const char *lhost = parse_tree->lhost ? parse_tree->lhost : user_runhost;
++ const char *shost = parse_tree->shost ? parse_tree->shost : user_srunhost;
++
++ return hostlist_matches_int(parse_tree, pw, lhost, shost, list);
+ }
+
+ /*
+diff --git a/plugins/sudoers/parse.h b/plugins/sudoers/parse.h
+index 7dcbdca..b17761a 100644
+--- a/plugins/sudoers/parse.h
++++ b/plugins/sudoers/parse.h
+@@ -265,6 +265,7 @@ struct sudoers_parse_tree {
+ struct userspec_list userspecs;
+ struct defaults_list defaults;
+ struct rbtree *aliases;
++ const char *shost, *lhost;
+ };
+
+ /* alias.c */
+@@ -290,7 +291,7 @@ void free_userspec(struct userspec *us);
+ void free_userspecs(struct userspec_list *usl);
+ void free_default(struct defaults *def, struct member_list **binding);
+ void free_defaults(struct defaults_list *defs);
+-void init_parse_tree(struct sudoers_parse_tree *parse_tree);
++void init_parse_tree(struct sudoers_parse_tree *parse_tree, const char *shost, const char *lhost);
+ void free_parse_tree(struct sudoers_parse_tree *parse_tree);
+ void reparent_parse_tree(struct sudoers_parse_tree *new_tree);
+
+diff --git a/plugins/sudoers/sssd.c b/plugins/sudoers/sssd.c
+index 085549f..fa9a930 100644
+--- a/plugins/sudoers/sssd.c
++++ b/plugins/sudoers/sssd.c
+@@ -552,7 +552,6 @@ sudo_sss_open(struct sudo_nss *nss)
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ debug_return_int(ENOMEM);
+ }
+- init_parse_tree(&handle->parse_tree);
+
+ /* Load symbols */
+ handle->ssslib = sudo_dso_load(path, SUDO_DSO_LAZY);
+@@ -610,8 +609,6 @@ sudo_sss_open(struct sudo_nss *nss)
+ debug_return_int(EFAULT);
+ }
+
+- nss->handle = handle;
+-
+ /*
+ * If runhost is the same as the local host, check for ipa_hostname
+ * in sssd.conf and use it in preference to user_runhost.
+@@ -623,6 +620,10 @@ sudo_sss_open(struct sudo_nss *nss)
+ }
+ }
+
++ /* The "parse tree" contains userspecs, defaults, aliases and hostnames. */
++ init_parse_tree(&handle->parse_tree, handle->ipa_host, handle->ipa_shost);
++ nss->handle = handle;
++
+ sudo_debug_printf(SUDO_DEBUG_DEBUG, "handle=%p", handle);
+
+ debug_return_int(0);
diff --git a/debian/patches/0017-Escape-control-characters-in-log-messages-and-sudore.patch b/debian/patches/0017-Escape-control-characters-in-log-messages-and-sudore.patch
new file mode 100644
index 0000000..ebd2c6e
--- /dev/null
+++ b/debian/patches/0017-Escape-control-characters-in-log-messages-and-sudore.patch
@@ -0,0 +1,663 @@
+From: "Todd C. Miller" <Todd.Miller@sudo.ws>
+Date: Wed, 18 Jan 2023 08:21:34 -0700
+Subject: Escape control characters in log messages and "sudoreplay -l"
+ output.
+
+The log message contains user-controlled strings that could
+include things like terminal control characters.
+Space characters in the command path are now also escaped.
+
+Command line arguments that contain spaces are surrounded with
+single quotes and any literal single quote or backslash characters
+are escaped with a backslash. This makes it possible to distinguish
+multiple command line arguments from a single argument that contains
+spaces.
+
+Issue found by Matthieu Barjole and Victor Cutillas of Synacktiv
+(https://synacktiv.com).
+
+origin: https://github.com/sudo-project/sudo/commit/334daf92b31b79ce68ed75e2ee14fca265f029ca
+bug-debian-security: https://security-tracker.debian.org/tracker/CVE-2023-28486
+bug-debian-security: https://security-tracker.debian.org/tracker/CVE-2023-28487
+bug-ubuntu-security: https://people.canonical.com/~ubuntu-security/cve/CVE-2023-28487
+bug-ubuntu-security: https://people.canonical.com/~ubuntu-security/cve/CVE-2023-28486
+---
+ doc/sudoers.man.in | 33 +++++++---
+ doc/sudoers.mdoc.in | 28 +++++++--
+ doc/sudoreplay.man.in | 9 +++
+ doc/sudoreplay.mdoc.in | 10 +++
+ include/sudo_compat.h | 6 ++
+ include/sudo_lbuf.h | 7 +++
+ lib/util/lbuf.c | 106 ++++++++++++++++++++++++++++++++
+ lib/util/util.exp.in | 1 +
+ plugins/sudoers/logging.c | 141 +++++++++++--------------------------------
+ plugins/sudoers/sudoreplay.c | 42 ++++++++++---
+ 10 files changed, 254 insertions(+), 129 deletions(-)
+
+diff --git a/doc/sudoers.man.in b/doc/sudoers.man.in
+index e2470b5..46bf8ea 100644
+--- a/doc/sudoers.man.in
++++ b/doc/sudoers.man.in
+@@ -4419,6 +4419,19 @@ can log events using either
+ syslog(3)
+ or a simple log file.
+ The log format is almost identical in both cases.
++Any control characters present in the log data are formatted in octal
++with a leading
++\(oq#\(cq
++character.
++For example, a horizontal tab is stored as
++\(oq#011\(cq
++and an embedded carriage return is stored as
++\(oq#015\(cq.
++In addition, space characters in the command path are stored as
++\(oq#040\(cq.
++Literal single quotes and backslash characters
++(\(oq\e\(cq)
++in command line arguments are escaped with a backslash.
+ .SS "Accepted command log entries"
+ Commands that sudo runs are logged using the following format (split
+ into multiple lines for readability):
+@@ -4499,7 +4512,7 @@ A list of environment variables specified on the command line,
+ if specified.
+ .TP 14n
+ command
+-The actual command that was executed.
++The actual command that was executed, including any command line arguments.
+ .PP
+ Messages are logged using the locale specified by
+ \fIsudoers_locale\fR,
+@@ -4735,17 +4748,21 @@ with a few important differences:
+ 1.\&
+ The
+ \fIprogname\fR
+-and
+-\fIhostname\fR
+-fields are not present.
++field is not present.
+ .TP 5n
+ 2.\&
+-If the
+-\fIlog_year\fR
+-option is enabled,
+-the date will also include the year.
++The
++\fIhostname\fR
++is only logged if the
++\fIlog_host\fR
++option is enabled.
+ .TP 5n
+ 3.\&
++The date does not include the year unless the
++\fIlog_year\fR
++option is enabled.
++.TP 5n
++4.\&
+ Lines that are longer than
+ \fIloglinelen\fR
+ characters (80 by default) are word-wrapped and continued on the
+diff --git a/doc/sudoers.mdoc.in b/doc/sudoers.mdoc.in
+index 2053b5d..208f9d3 100644
+--- a/doc/sudoers.mdoc.in
++++ b/doc/sudoers.mdoc.in
+@@ -4120,6 +4120,19 @@ can log events using either
+ .Xr syslog 3
+ or a simple log file.
+ The log format is almost identical in both cases.
++Any control characters present in the log data are formatted in octal
++with a leading
++.Ql #
++character.
++For example, a horizontal tab is stored as
++.Ql #011
++and an embedded carriage return is stored as
++.Ql #015 .
++In addition, space characters in the command path are stored as
++.Ql #040 .
++Literal single quotes and backslash characters
++.Pq Ql \e
++in command line arguments are escaped with a backslash.
+ .Ss Accepted command log entries
+ Commands that sudo runs are logged using the following format (split
+ into multiple lines for readability):
+@@ -4187,7 +4200,7 @@ option is enabled.
+ A list of environment variables specified on the command line,
+ if specified.
+ .It command
+-The actual command that was executed.
++The actual command that was executed, including any command line arguments.
+ .El
+ .Pp
+ Messages are logged using the locale specified by
+@@ -4409,14 +4422,17 @@ with a few important differences:
+ .It
+ The
+ .Em progname
+-and
++field is not present.
++.It
++The
+ .Em hostname
+-fields are not present.
++is only logged if the
++.Em log_host
++option is enabled.
+ .It
+-If the
++The date does not include the year unless the
+ .Em log_year
+-option is enabled,
+-the date will also include the year.
++option is enabled.
+ .It
+ Lines that are longer than
+ .Em loglinelen
+diff --git a/doc/sudoreplay.man.in b/doc/sudoreplay.man.in
+index 7bb2da6..6e28807 100644
+--- a/doc/sudoreplay.man.in
++++ b/doc/sudoreplay.man.in
+@@ -138,6 +138,15 @@ In this mode,
+ will list available sessions in a format similar to the
+ \fBsudo\fR
+ log file format, sorted by file name (or sequence number).
++Any control characters present in the log data are formated in octal
++with a leading
++\(oq#\(cq
++character.
++For example, a horizontal tab is displayed as
++\(oq#011\(cq
++and an embedded carriage return is displayed as
++\(oq#015\(cq.
++.sp
+ If a
+ \fIsearch expression\fR
+ is specified, it will be used to restrict the IDs that are displayed.
+diff --git a/doc/sudoreplay.mdoc.in b/doc/sudoreplay.mdoc.in
+index 4eefaf6..91ca3af 100644
+--- a/doc/sudoreplay.mdoc.in
++++ b/doc/sudoreplay.mdoc.in
+@@ -131,6 +131,16 @@ In this mode,
+ will list available sessions in a format similar to the
+ .Nm sudo
+ log file format, sorted by file name (or sequence number).
++Any control characters present in the log data are formatted in octal
++with a leading
++.Ql #
++character.
++For example, a horizontal tab is displayed as
++.Ql #011
++and an embedded carriage return is displayed as
++.Ql #015 .
++Space characters in the command name and arguments are also formatted in octal.
++.Pp
+ If a
+ .Ar search expression
+ is specified, it will be used to restrict the IDs that are displayed.
+diff --git a/include/sudo_compat.h b/include/sudo_compat.h
+index 5c32840..c4366c0 100644
+--- a/include/sudo_compat.h
++++ b/include/sudo_compat.h
+@@ -77,6 +77,12 @@
+ # endif
+ #endif
+
++#ifdef HAVE_FALLTHROUGH_ATTRIBUTE
++# define FALLTHROUGH __attribute__((__fallthrough__))
++#else
++# define FALLTHROUGH do { } while (0)
++#endif
++
+ /*
+ * Given the pointer x to the member m of the struct s, return
+ * a pointer to the containing structure.
+diff --git a/include/sudo_lbuf.h b/include/sudo_lbuf.h
+index 8e07063..c2c1fa1 100644
+--- a/include/sudo_lbuf.h
++++ b/include/sudo_lbuf.h
+@@ -34,9 +34,15 @@ struct sudo_lbuf {
+
+ typedef int (*sudo_lbuf_output_t)(const char *);
+
++/* Flags for sudo_lbuf_append_esc() */
++#define LBUF_ESC_CNTRL 0x01
++#define LBUF_ESC_BLANK 0x02
++#define LBUF_ESC_QUOTE 0x04
++
+ __dso_public void sudo_lbuf_init_v1(struct sudo_lbuf *lbuf, sudo_lbuf_output_t output, int indent, const char *continuation, int cols);
+ __dso_public void sudo_lbuf_destroy_v1(struct sudo_lbuf *lbuf);
+ __dso_public bool sudo_lbuf_append_v1(struct sudo_lbuf *lbuf, const char *fmt, ...) __printflike(2, 3);
++__dso_public bool sudo_lbuf_append_esc_v1(struct sudo_lbuf *lbuf, int flags, const char *fmt, ...) __printflike(3, 4);
+ __dso_public bool sudo_lbuf_append_quoted_v1(struct sudo_lbuf *lbuf, const char *set, const char *fmt, ...) __printflike(3, 4);
+ __dso_public void sudo_lbuf_print_v1(struct sudo_lbuf *lbuf);
+ __dso_public bool sudo_lbuf_error_v1(struct sudo_lbuf *lbuf);
+@@ -45,6 +51,7 @@ __dso_public void sudo_lbuf_clearerr_v1(struct sudo_lbuf *lbuf);
+ #define sudo_lbuf_init(_a, _b, _c, _d, _e) sudo_lbuf_init_v1((_a), (_b), (_c), (_d), (_e))
+ #define sudo_lbuf_destroy(_a) sudo_lbuf_destroy_v1((_a))
+ #define sudo_lbuf_append sudo_lbuf_append_v1
++#define sudo_lbuf_append_esc sudo_lbuf_append_esc_v1
+ #define sudo_lbuf_append_quoted sudo_lbuf_append_quoted_v1
+ #define sudo_lbuf_print(_a) sudo_lbuf_print_v1((_a))
+ #define sudo_lbuf_error(_a) sudo_lbuf_error_v1((_a))
+diff --git a/lib/util/lbuf.c b/lib/util/lbuf.c
+index 5e48258..39d4ddd 100644
+--- a/lib/util/lbuf.c
++++ b/lib/util/lbuf.c
+@@ -90,6 +90,112 @@ sudo_lbuf_expand(struct sudo_lbuf *lbuf, int extra)
+ debug_return_bool(true);
+ }
+
++/*
++ * Escape a character in octal form (#0n) and store it as a string
++ * in buf, which must have at least 6 bytes available.
++ * Returns the length of buf, not counting the terminating NUL byte.
++ */
++static int
++escape(unsigned char ch, char *buf)
++{
++ const int len = ch < 0100 ? (ch < 010 ? 3 : 4) : 5;
++
++ /* Work backwards from the least significant digit to most significant. */
++ switch (len) {
++ case 5:
++ buf[4] = (ch & 7) + '0';
++ ch >>= 3;
++ FALLTHROUGH;
++ case 4:
++ buf[3] = (ch & 7) + '0';
++ ch >>= 3;
++ FALLTHROUGH;
++ case 3:
++ buf[2] = (ch & 7) + '0';
++ buf[1] = '0';
++ buf[0] = '#';
++ break;
++ }
++ buf[len] = '\0';
++
++ return len;
++}
++
++/*
++ * Parse the format and append strings, only %s and %% escapes are supported.
++ * Any non-printable characters are escaped in octal as #0nn.
++ */
++bool
++sudo_lbuf_append_esc_v1(struct sudo_lbuf *lbuf, int flags, const char *fmt, ...)
++{
++ unsigned int saved_len = lbuf->len;
++ bool ret = false;
++ const char *s;
++ va_list ap;
++ debug_decl(sudo_lbuf_append_esc, SUDO_DEBUG_UTIL);
++
++ if (sudo_lbuf_error(lbuf))
++ debug_return_bool(false);
++
++#define should_escape(ch) \
++ ((ISSET(flags, LBUF_ESC_CNTRL) && iscntrl((unsigned char)ch)) || \
++ (ISSET(flags, LBUF_ESC_BLANK) && isblank((unsigned char)ch)))
++#define should_quote(ch) \
++ (ISSET(flags, LBUF_ESC_QUOTE) && (ch == '\'' || ch == '\\'))
++
++ va_start(ap, fmt);
++ while (*fmt != '\0') {
++ if (fmt[0] == '%' && fmt[1] == 's') {
++ if ((s = va_arg(ap, char *)) == NULL)
++ s = "(NULL)";
++ while (*s != '\0') {
++ if (should_escape(*s)) {
++ if (!sudo_lbuf_expand(lbuf, sizeof("#0177") - 1))
++ goto done;
++ lbuf->len += escape(*s++, lbuf->buf + lbuf->len);
++ continue;
++ }
++ if (should_quote(*s)) {
++ if (!sudo_lbuf_expand(lbuf, 2))
++ goto done;
++ lbuf->buf[lbuf->len++] = '\\';
++ lbuf->buf[lbuf->len++] = *s++;
++ continue;
++ }
++ if (!sudo_lbuf_expand(lbuf, 1))
++ goto done;
++ lbuf->buf[lbuf->len++] = *s++;
++ }
++ fmt += 2;
++ continue;
++ }
++ if (should_escape(*fmt)) {
++ if (!sudo_lbuf_expand(lbuf, sizeof("#0177") - 1))
++ goto done;
++ if (*fmt == '\'') {
++ lbuf->buf[lbuf->len++] = '\\';
++ lbuf->buf[lbuf->len++] = *fmt++;
++ } else {
++ lbuf->len += escape(*fmt++, lbuf->buf + lbuf->len);
++ }
++ continue;
++ }
++ if (!sudo_lbuf_expand(lbuf, 1))
++ goto done;
++ lbuf->buf[lbuf->len++] = *fmt++;
++ }
++ ret = true;
++
++done:
++ if (!ret)
++ lbuf->len = saved_len;
++ if (lbuf->size != 0)
++ lbuf->buf[lbuf->len] = '\0';
++ va_end(ap);
++
++ debug_return_bool(ret);
++}
++
+ /*
+ * Parse the format and append strings, only %s and %% escapes are supported.
+ * Any characters in set are quoted with a backslash.
+diff --git a/lib/util/util.exp.in b/lib/util/util.exp.in
+index 510c34c..b683d84 100644
+--- a/lib/util/util.exp.in
++++ b/lib/util/util.exp.in
+@@ -80,6 +80,7 @@ sudo_gethostname_v1
+ sudo_gettime_awake_v1
+ sudo_gettime_mono_v1
+ sudo_gettime_real_v1
++sudo_lbuf_append_esc_v1
+ sudo_lbuf_append_quoted_v1
+ sudo_lbuf_append_v1
+ sudo_lbuf_clearerr_v1
+diff --git a/plugins/sudoers/logging.c b/plugins/sudoers/logging.c
+index 9562609..bb31861 100644
+--- a/plugins/sudoers/logging.c
++++ b/plugins/sudoers/logging.c
+@@ -56,6 +56,7 @@
+ #include <syslog.h>
+
+ #include "sudoers.h"
++#include "sudo_lbuf.h"
+
+ #ifndef HAVE_GETADDRINFO
+ # include "compat/getaddrinfo.h"
+@@ -877,14 +878,6 @@ should_mail(int status)
+ (def_mail_no_perms && !ISSET(status, VALIDATE_SUCCESS)));
+ }
+
+-#define LL_TTY_STR "TTY="
+-#define LL_CWD_STR "PWD=" /* XXX - should be CWD= */
+-#define LL_USER_STR "USER="
+-#define LL_GROUP_STR "GROUP="
+-#define LL_ENV_STR "ENV="
+-#define LL_CMND_STR "COMMAND="
+-#define LL_TSID_STR "TSID="
+-
+ #define IS_SESSID(s) ( \
+ isalnum((unsigned char)(s)[0]) && isalnum((unsigned char)(s)[1]) && \
+ (s)[2] == '/' && \
+@@ -899,14 +892,16 @@ should_mail(int status)
+ static char *
+ new_logline(const char *message, const char *errstr)
+ {
+- char *line = NULL, *evstr = NULL;
+ #ifndef SUDOERS_NO_SEQ
+ char sessid[7];
+ #endif
+ const char *tsid = NULL;
+- size_t len = 0;
++ struct sudo_lbuf lbuf;
++ int i;
+ debug_decl(new_logline, SUDOERS_DEBUG_LOGGING)
+
++ sudo_lbuf_init(&lbuf, NULL, 0, NULL, 0);
++
+ #ifndef SUDOERS_NO_SEQ
+ /* A TSID may be a sudoers-style session ID or a free-form string. */
+ if (sudo_user.iolog_file != NULL) {
+@@ -926,119 +921,55 @@ new_logline(const char *message, const char *errstr)
+ #endif
+
+ /*
+- * Compute line length
++ * Format the log line as an lbuf, escaping control characters in
++ * octal form (#0nn). Error checking (ENOMEM) is done at the end.
+ */
+- if (message != NULL)
+- len += strlen(message) + 3;
+- if (errstr != NULL)
+- len += strlen(errstr) + 3;
+- len += sizeof(LL_TTY_STR) + 2 + strlen(user_tty);
+- len += sizeof(LL_CWD_STR) + 2 + strlen(user_cwd);
+- if (runas_pw != NULL)
+- len += sizeof(LL_USER_STR) + 2 + strlen(runas_pw->pw_name);
+- if (runas_gr != NULL)
+- len += sizeof(LL_GROUP_STR) + 2 + strlen(runas_gr->gr_name);
+- if (tsid != NULL)
+- len += sizeof(LL_TSID_STR) + 2 + strlen(tsid);
+- if (sudo_user.env_vars != NULL) {
+- size_t evlen = 0;
+- char * const *ep;
+-
+- for (ep = sudo_user.env_vars; *ep != NULL; ep++)
+- evlen += strlen(*ep) + 1;
+- if (evlen != 0) {
+- if ((evstr = malloc(evlen)) == NULL)
+- goto oom;
+- evstr[0] = '\0';
+- for (ep = sudo_user.env_vars; *ep != NULL; ep++) {
+- strlcat(evstr, *ep, evlen);
+- strlcat(evstr, " ", evlen); /* NOTE: last one will fail */
+- }
+- len += sizeof(LL_ENV_STR) + 2 + evlen;
+- }
+- }
+- if (user_cmnd != NULL) {
+- /* Note: we log "sudo -l command arg ..." as "list command arg ..." */
+- len += sizeof(LL_CMND_STR) - 1 + strlen(user_cmnd);
+- if (ISSET(sudo_mode, MODE_CHECK))
+- len += sizeof("list ") - 1;
+- if (user_args != NULL)
+- len += strlen(user_args) + 1;
+- }
+-
+- /*
+- * Allocate and build up the line.
+- */
+- if ((line = malloc(++len)) == NULL)
+- goto oom;
+- line[0] = '\0';
+
+ if (message != NULL) {
+- if (strlcat(line, message, len) >= len ||
+- strlcat(line, errstr ? " : " : " ; ", len) >= len)
+- goto toobig;
++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "%s%s", message,
++ errstr ? " : " : " ; ");
+ }
+ if (errstr != NULL) {
+- if (strlcat(line, errstr, len) >= len ||
+- strlcat(line, " ; ", len) >= len)
+- goto toobig;
++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "%s ; ", errstr);
++ }
++ if (user_tty != NULL) {
++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "TTY=%s ; ", user_tty);
++ }
++ if (user_cwd != NULL) {
++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "PWD=%s ; ", user_cwd);
+ }
+- if (strlcat(line, LL_TTY_STR, len) >= len ||
+- strlcat(line, user_tty, len) >= len ||
+- strlcat(line, " ; ", len) >= len)
+- goto toobig;
+- if (strlcat(line, LL_CWD_STR, len) >= len ||
+- strlcat(line, user_cwd, len) >= len ||
+- strlcat(line, " ; ", len) >= len)
+- goto toobig;
+ if (runas_pw != NULL) {
+- if (strlcat(line, LL_USER_STR, len) >= len ||
+- strlcat(line, runas_pw->pw_name, len) >= len ||
+- strlcat(line, " ; ", len) >= len)
+- goto toobig;
++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "USER=%s ; ",
++ runas_pw->pw_name);
+ }
+ if (runas_gr != NULL) {
+- if (strlcat(line, LL_GROUP_STR, len) >= len ||
+- strlcat(line, runas_gr->gr_name, len) >= len ||
+- strlcat(line, " ; ", len) >= len)
+- goto toobig;
++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "GROUP=%s ; ",
++ runas_gr->gr_name);
+ }
+ if (tsid != NULL) {
+- if (strlcat(line, LL_TSID_STR, len) >= len ||
+- strlcat(line, tsid, len) >= len ||
+- strlcat(line, " ; ", len) >= len)
+- goto toobig;
++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "TSID=%s ; ", tsid);
+ }
+- if (evstr != NULL) {
+- if (strlcat(line, LL_ENV_STR, len) >= len ||
+- strlcat(line, evstr, len) >= len ||
+- strlcat(line, " ; ", len) >= len)
+- goto toobig;
+- free(evstr);
+- evstr = NULL;
++ if (sudo_user.env_vars != NULL) {
++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "ENV=%s", sudo_user.env_vars[0]);
++ for (i = 1; sudo_user.env_vars[i] != NULL; i++) {
++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, " %s",
++ sudo_user.env_vars[i]);
++ }
+ }
+ if (user_cmnd != NULL) {
+- if (strlcat(line, LL_CMND_STR, len) >= len)
+- goto toobig;
+- if (ISSET(sudo_mode, MODE_CHECK) && strlcat(line, "list ", len) >= len)
+- goto toobig;
+- if (strlcat(line, user_cmnd, len) >= len)
+- goto toobig;
++ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL|LBUF_ESC_BLANK,
++ "COMMAND=%s", user_cmnd);
+ if (user_args != NULL) {
+- if (strlcat(line, " ", len) >= len ||
+- strlcat(line, user_args, len) >= len)
+- goto toobig;
++ sudo_lbuf_append_esc(&lbuf,
++ LBUF_ESC_CNTRL|LBUF_ESC_QUOTE,
++ " %s", user_args);
+ }
+ }
+
+- debug_return_str(line);
+-oom:
+- free(evstr);
++ if (!sudo_lbuf_error(&lbuf))
++ debug_return_str(lbuf.buf);
++
++ sudo_lbuf_destroy(&lbuf);
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ debug_return_str(NULL);
+-toobig:
+- free(evstr);
+- free(line);
+- sudo_warnx(U_("internal error, %s overflow"), __func__);
+- debug_return_str(NULL);
+ }
+diff --git a/plugins/sudoers/sudoreplay.c b/plugins/sudoers/sudoreplay.c
+index a447cd0..01bc53a 100644
+--- a/plugins/sudoers/sudoreplay.c
++++ b/plugins/sudoers/sudoreplay.c
+@@ -69,6 +69,7 @@
+ #include "sudo_conf.h"
+ #include "sudo_debug.h"
+ #include "sudo_event.h"
++#include "sudo_lbuf.h"
+ #include "sudo_util.h"
+
+ #ifdef HAVE_GETOPT_LONG
+@@ -1347,7 +1348,8 @@ match_expr(struct search_node_list *head, struct log_info *log, bool last_match)
+ }
+
+ static int
+-list_session(char *logfile, regex_t *re, const char *user, const char *tty)
++list_session(struct sudo_lbuf *lbuf, char *logfile, regex_t *re,
++ const char *user, const char *tty)
+ {
+ char idbuf[7], *idstr, *cp;
+ const char *timestr;
+@@ -1380,16 +1382,32 @@ list_session(char *logfile, regex_t *re, const char *user, const char *tty)
+ }
+ /* XXX - print rows + cols? */
+ timestr = get_timestr(li->tstamp, 1);
+- printf("%s : %s : TTY=%s ; CWD=%s ; USER=%s ; ",
+- timestr ? timestr : "invalid date",
+- li->user, li->tty, li->cwd, li->runas_user);
+- if (li->runas_group)
+- printf("GROUP=%s ; ", li->runas_group);
+- printf("TSID=%s ; COMMAND=%s\n", idstr, li->cmd);
+-
+- ret = 0;
++ sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "%s : %s : ",
++ timestr ? timestr : "invalid date", li->user);
++ if (li->tty != NULL) {
++ sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "TTY=%s ; ",
++ li->tty);
++ }
++ if (li->cwd != NULL) {
++ sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "CWD=%s ; ",
++ li->cwd);
++ }
++ sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "USER=%s ; ", li->runas_user);
++ if (li->runas_group != NULL) {
++ sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "GROUP=%s ; ",
++ li->runas_group);
++ }
++ sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "TSID=%s ; ", idstr);
++ sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "COMMAND=%s",
++ li->cmd);
+
++ if (!sudo_lbuf_error(lbuf)) {
++ puts(lbuf->buf);
++ ret = 0;
++ }
+ done:
++ lbuf->error = 0;
++ lbuf->len = 0;
+ free_log_info(li);
+ debug_return_int(ret);
+ }
+@@ -1409,6 +1427,7 @@ find_sessions(const char *dir, regex_t *re, const char *user, const char *tty)
+ DIR *d;
+ struct dirent *dp;
+ struct stat sb;
++ struct sudo_lbuf lbuf;
+ size_t sdlen, sessions_len = 0, sessions_size = 0;
+ unsigned int i;
+ int len;
+@@ -1420,6 +1439,8 @@ find_sessions(const char *dir, regex_t *re, const char *user, const char *tty)
+ #endif
+ debug_decl(find_sessions, SUDO_DEBUG_UTIL)
+
++ sudo_lbuf_init(&lbuf, NULL, 0, NULL, 0);
++
+ d = opendir(dir);
+ if (d == NULL)
+ sudo_fatal(U_("unable to open %s"), dir);
+@@ -1479,7 +1500,7 @@ find_sessions(const char *dir, regex_t *re, const char *user, const char *tty)
+
+ /* Check for dir with a log file. */
+ if (lstat(pathbuf, &sb) == 0 && S_ISREG(sb.st_mode)) {
+- list_session(pathbuf, re, user, tty);
++ list_session(&lbuf, pathbuf, re, user, tty);
+ } else {
+ /* Strip off "/log" and recurse if a dir. */
+ pathbuf[sdlen + len - 4] = '\0';
+@@ -1490,6 +1511,7 @@ find_sessions(const char *dir, regex_t *re, const char *user, const char *tty)
+ }
+ free(sessions);
+ }
++ sudo_lbuf_destroy(&lbuf);
+
+ debug_return_int(0);
+ }
diff --git a/debian/patches/0018-Add-missing-separator-between-environment-variables-.patch b/debian/patches/0018-Add-missing-separator-between-environment-variables-.patch
new file mode 100644
index 0000000..ee38855
--- /dev/null
+++ b/debian/patches/0018-Add-missing-separator-between-environment-variables-.patch
@@ -0,0 +1,24 @@
+From: "Todd C. Miller" <Todd.Miller@sudo.ws>
+Date: Mon, 13 Mar 2023 08:04:32 -0600
+Subject: Add missing " ;
+ " separator between environment variables and command.
+
+This is a regression introduced in sudo 1.9.13. GitHub issue #254.
+
+bug: https://github.com/sudo-project/sudo/issues/254
+---
+ plugins/sudoers/logging.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/plugins/sudoers/logging.c b/plugins/sudoers/logging.c
+index bb31861..65ee04e 100644
+--- a/plugins/sudoers/logging.c
++++ b/plugins/sudoers/logging.c
+@@ -955,6 +955,7 @@ new_logline(const char *message, const char *errstr)
+ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, " %s",
+ sudo_user.env_vars[i]);
+ }
++ sudo_lbuf_append(&lbuf, " ; ");
+ }
+ if (user_cmnd != NULL) {
+ sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL|LBUF_ESC_BLANK,
diff --git a/debian/patches/series b/debian/patches/series
index 286e759..43c82cc 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -11,5 +11,7 @@ Fix-potential-buffer-overflow-when-unescaping-backsl.patch
Fix-the-memset-offset-when-converting-a-v1-timestamp.patch
Don-t-assume-that-argv-is-allocated-as-a-single-flat.patch
CVE-2021-23239.patch
-
CVE-2023-22809.patch
+0016-CVE-2023-7090-Fix-special-handling-of-ipa_hostname-t.patch
+0017-Escape-control-characters-in-log-messages-and-sudore.patch
+0018-Add-missing-separator-between-environment-variables-.patch
diff --git a/debian/rules b/debian/rules
index 2b5375b..80f329d 100755
--- a/debian/rules
+++ b/debian/rules
@@ -28,12 +28,13 @@ configure-stamp: reconf-stamp
# simple version
NROFFPROG=/usr/bin/nroff CFLAGS="$(CFLAGS)" \
- CPPFLAGS="$(CPPFLAGS)" LDFLAGS="$(LDFLAGS)" \
+ CPPFLAGS="$(CPPFLAGS)" LDFLAGS="$(LDFLAGS)" devdir=. \
dh_auto_configure --builddirectory=build-simple -- \
-v \
--with-all-insults \
--with-pam \
--with-fqdn \
+ --with-devel=yes \
--with-logging=syslog \
--with-logfac=authpriv \
--with-env-editor \
@@ -56,6 +57,7 @@ configure-stamp: reconf-stamp
-v \
--with-all-insults \
--with-pam \
+ --with-devel=yes \
--with-ldap \
--with-fqdn \
--with-logging=syslog \
@@ -82,12 +84,11 @@ build-arch: build-stamp
build-indep: build-stamp
build-stamp: configure-stamp
dh_testdir
-
- $(MAKE) -C build-simple
- $(MAKE) -C build-ldap
+ cd build-simple && $(MAKE)
+ cd build-ldap && $(MAKE)
ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))
- $(MAKE) -C build-simple check
+ cd build-simple && $(MAKE) check
endif
touch build-stamp
@@ -98,6 +99,7 @@ clean:
rm -f configure-stamp build-stamp
rm -rf build-simple build-ldap
rm -f config.cache
+ rm -f plugins/sudoers/gram.c plugins/sudoers/gram.h
dh_clean
install: build-stamp
diff --git a/debian/.gitlab-ci.yml b/debian/salsa-ci.yml
index efd58ac..efd58ac 100644
--- a/debian/.gitlab-ci.yml
+++ b/debian/salsa-ci.yml
diff --git a/debian/tests/01-getroot b/debian/tests/01-getroot
new file mode 100755
index 0000000..4edef3e
--- /dev/null
+++ b/debian/tests/01-getroot
@@ -0,0 +1,100 @@
+#!/bin/sh
+
+set -e
+
+# set a root password so that we can later replace sudo with sudo-ldap
+# see #1001858
+passwd=$(getent shadow root|cut -f2 -d:)
+passwd1=$(echo "$passwd" |cut -c1)
+# Note: we do need the 'xfoo' syntax here, since POSIX special-cases
+# the $passwd value '!' as negation.
+if [ "x$passwd" = "x*" ] || [ "x$passwd1" = "x!" ]; then
+ echo "root:rootpassword" | chpasswd
+fi
+
+TESTNR="01"
+BASEDIR="$(pwd)/debian/tests"
+COMMONDIR="${BASEDIR}/common"
+DIR="${BASEDIR}/${TESTNR}"
+PATH="/bin:/usr/bin:/sbin:/usr/sbin"
+ACCTA="test${TESTNR}a"
+ACCTB="test${TESTNR}b"
+PASSWD="test${TESTNR}23456"
+HOMEDIRA="/home/${ACCTA}"
+HOMEDIRB="/home/${ACCTB}"
+LDIFDIR="${DIR}/ldif"
+
+trap '
+ deluser --remove-home "${ACCTA}" 2>/dev/null || true
+ deluser --remove-home "${ACCTB}" 2>/dev/null || true
+' 0 INT QUIT ABRT PIPE TERM
+
+printf > /etc/hosts "127.0.1.1 %s\n" "$(hostname)"
+cat /etc/hosts
+
+printf "========= test %s\.1: account group member, correct password\n" "${TESTNR}"
+deluser ${ACCTA} 2>/dev/null || true
+adduser --disabled-password --home "${HOMEDIRA}" --gecos "" "${ACCTA}"
+printf "%s:%s\n" "${ACCTA}" "${PASSWD}" | chpasswd
+adduser "${ACCTA}" sudo
+RET=0
+printf "trying %s with correct password\n" "${ACCTA}"
+su - "${ACCTA}" -c "${COMMONDIR}/asuser ${PASSWD}" || RET=$?
+printf "%s with correct password, return value %s\n" "${ACCTA}" "${RET}"
+if [ "$(cat ${HOMEDIRA}/stdout)" != "0" ]; then
+ echo >&2 id -u did not give 0
+ printf >&2 "stdout:\n"
+ cat >&2 ${HOMEDIRA}/stdout
+ printf >&2 "stderr:\n"
+ cat >&2 ${HOMEDIRA}/stderr
+ printf >&2 "exit code %s\n" "${RET}"
+ printf >&2 "exit 1\n" "${RET}"
+ exit 1
+fi
+
+printf "========= test %s\.2: account group member, wrong password\n" "${TESTNR}"
+rm -f "${HOMEDIRA}/std*"
+RET=0
+printf "trying %s with wrong password\n" "${ACCTA}"
+su - "${ACCTA}" -c "${COMMONDIR}/asuser wrongpasswd" || RET=$?
+printf "%s with wrong password, return value %s\n" "${ACCTA}" "${RET}"
+head -n-0 ${HOMEDIRA}/stdout ${HOMEDIRA}/stderr
+printf -- "\n-------\n"
+for string in "[sudo] password for ${ACCTA}" "Sorry, try again" "sudo: no password was provided" "sudo: 1 incorrect password attempt"; do
+ if ! grep -F "${string}" ${HOMEDIRA}/stderr; then
+ printf "%s missing in stderr output\n" "${string}"
+ printf >&2 "stdout:\n"
+ cat >&2 ${HOMEDIRA}/stdout
+ printf >&2 "stderr:\n"
+ cat >&2 ${HOMEDIRA}/stderr
+ printf >&2 "\nexit code %s\n" "${RET}"
+ printf >&2 -- "------\n exit 1\n"
+ exit 1
+ fi
+done
+
+printf "========= test %s\.3: account not group member, correct password\n" "${TESTNR}"
+deluser ${ACCTB} 2>/dev/null || true
+adduser --disabled-password --home "${HOMEDIRB}" --gecos "" "${ACCTB}"
+printf "%s:%s\n" "${ACCTB}" "${PASSWD}" | chpasswd
+RET=0
+printf "trying %s (no sudo membership) with correct password\n" "${ACCTB}"
+su - "${ACCTB}" -c "${COMMONDIR}/asuser ${PASSWD}" || RET=$?
+printf "%s with correct password, return value %s\n" "${ACCTB}" "${RET}"
+head -n-0 ${HOMEDIRB}/stdout ${HOMEDIRA}/stderr
+printf -- "\n-------\n"
+for string in "[sudo] password for ${ACCTB}" "${ACCTB} is not in the sudoers file"; do
+ if ! grep -F "${string}" ${HOMEDIRB}/stderr; then
+ printf "%s missing in stderr output\n" "${string}"
+ printf >&2 "stdout:\n"
+ cat >&2 ${HOMEDIRB}/stdout
+ printf >&2 "stderr:\n"
+ cat >&2 ${HOMEDIRB}/stderr
+ printf >&2 "\nexit code %s\n" "${RET}"
+ printf >&2 -- "------\n exit 1\n"
+ exit 1
+ fi
+done
+
+printf "test series sucessful, exit 0\n"
+exit 0
diff --git a/debian/tests/02-1003969-audit-no-resolve b/debian/tests/02-1003969-audit-no-resolve
new file mode 100755
index 0000000..3fc32aa
--- /dev/null
+++ b/debian/tests/02-1003969-audit-no-resolve
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+set -e
+
+TESTNR="02"
+BASEDIR="$(pwd)/debian/tests"
+COMMONDIR="${BASEDIR}/common"
+DIR="${BASEDIR}/${TESTNR}"
+PATH="/bin:/usr/bin:/sbin:/usr/sbin"
+ACCTA="test${TESTNR}a"
+ACCTB="test${TESTNR}b"
+PASSWD="test${TESTNR}23456"
+HOMEDIRA="/root"
+LDIFDIR="${DIR}/ldif"
+
+trap '
+ printf "\ntrap handler\n"
+ mv /etc/resolv.conf.disabled /etc/resolv.conf || true
+ mv /etc/hosts.disabled /etc/hosts || true
+' 0 INT QUIT ABRT PIPE TERM
+
+printf "========= test %s\.1: sudo to nobody\n" "${TESTNR}"
+mv /etc/resolv.conf /etc/resolv.conf.disabled
+mv /etc/hosts /etc/hosts.disabled
+RET=0
+printf "trying sudo to nobody\n"
+cd "${HOMEDIRA}"
+${COMMONDIR}/asuser "" nobody || RET=$?
+printf "sudo to nobody, return value %s\n" "${RET}"
+STDERRLENGTH="$(cat ${HOMEDIRA}/stderr | grep -vE 'sudo: unable to resolve host [^:]+: Temporary failure in name resolution' | wc -l)"
+if [ "${STDERRLENGTH}" != "0" ]; then
+ echo >&2 non-empty stderr
+ printf >&2 "stdout:\n"
+ cat >&2 ${HOMEDIRA}/stdout
+ printf >&2 "stderr:\n"
+ cat >&2 ${HOMEDIRA}/stderr
+ printf >&2 "exit code %s\n" "${RET}"
+ printf >&2 "exit 1\n" "${RET}"
+ exit 1
+fi
+
+printf "test series sucessful, exit 0\n"
+exit 0
diff --git a/debian/tests/03-getroot-ldap b/debian/tests/03-getroot-ldap
new file mode 100755
index 0000000..f50be3a
--- /dev/null
+++ b/debian/tests/03-getroot-ldap
@@ -0,0 +1,132 @@
+#!/bin/sh
+
+set -e
+
+TESTNR="03"
+BASEDIR="$(pwd)/debian/tests"
+COMMONDIR="${BASEDIR}/common"
+DIR="${BASEDIR}/${TESTNR}"
+PATH="/bin:/usr/bin:/sbin:/usr/sbin"
+ACCTA="test${TESTNR}a"
+ACCTB="test${TESTNR}b"
+PASSWD="test${TESTNR}23456"
+HOMEDIRA="/home/${ACCTA}"
+HOMEDIRB="/home/${ACCTB}"
+LDIFDIR="${DIR}/ldif"
+
+trap '
+ kill $(pidof slapd) 2>/dev/null || true
+ deluser --remove-home "${ACCTA}" 2>/dev/null || true
+ deluser --remove-home "${ACCTB}" 2>/dev/null || true
+ mv /etc/disabled.sudoers /etc/sudoers 2>/dev/null || true
+' 0 INT QUIT ABRT PIPE TERM
+
+if ! grep -q '^slapd: ALL' /etc/hosts.allow; then
+ echo "slapd: ALL" >> /etc/hosts.allow
+fi
+
+< ${LDIFDIR}/debconf debconf-set-selections
+printf "clean up ldap database ... "
+rm -rf /var/lib/ldap/*.mdb
+printf "reconfigure slapd ... "
+DEBIAN_FRONTEND=noninteractive dpkg-reconfigure -pcritical slapd 2>/dev/null
+if ! grep -q '^slapd: ALL$' /etc/hosts.allow; then
+ echo "slapd: ALL" >> /etc/hosts.allow
+fi
+printf "start slapd ... "
+slapd -h 'ldap://127.0.0.1:11389/ ldapi:///' -g openldap -u openldap -F /etc/ldap/slapd.d
+echo "URI ldap://127.0.0.1:11389" > /etc/ldap/ldap.conf
+# ldapsearch -x -LLL -s base -b "" namingContexts should work here
+printf "add sudo schema to slapd ... "
+< /usr/share/doc/sudo-ldap/schema.olcSudo ldapadd -Y EXTERNAL -H ldapi:/// 2>/dev/null
+printf "add sudo group ... "
+< ${LDIFDIR}/container.ldif ldapadd -x -D 'cn=admin,dc=example,dc=com' -w ldappw 2>/dev/null
+if ! grep -q '^sudoers: ldap$' /etc/nsswitch.conf; then
+ sed -i '/^sudoers.*/d' /etc/nsswitch.conf
+ echo "sudoers: ldap" >> /etc/nsswitch.conf
+fi
+touch /etc/ldap/ldap.conf
+if ! grep -q '^sudoers_base ou=SUDOers,dc=example,dc=com' /etc/ldap/ldap.conf; then
+ echo "sudoers_base ou=SUDOers,dc=example,dc=com" >> /etc/ldap/ldap.conf
+fi
+printf "reconfigure sudo-ldap (#1001851) ... "
+DEBIAN_FRONTEND=noninteractive dpkg-reconfigure -pcritical sudo-ldap 2>/dev/null
+printf "cvtsudoers into sudoers.ldif ... "
+cvtsudoers -b ou=SUDOers,dc=example,dc=com -o ${LDIFDIR}/sudoers.ldif /etc/sudoers
+printf "\n cat sudoers.ldif\n"
+cat ${LDIFDIR}/sudoers.ldif
+printf "pull sudoers.ldif into ldap ..."
+< ${LDIFDIR}/sudoers.ldif ldapadd -x -D 'cn=admin,dc=example,dc=com' -w ldappw
+# ldapsearch -x -LLL -b "ou=SUDOers,dc=example,dc=com" should work here
+printf "move away sudoers ...\n"
+mv /etc/sudoers /etc/disabled.sudoers
+
+
+printf "========= test %s\.1: account group member, correct password\n" "${TESTNR}"
+printf > /etc/hosts "127.0.1.1 %s\n" "$(hostname)"
+deluser ${ACCTA} 2>/dev/null || true
+adduser --disabled-password --home "${HOMEDIRA}" --gecos "" "${ACCTA}"
+printf "%s:%s\n" "${ACCTA}" "${PASSWD}" | chpasswd
+adduser "${ACCTA}" sudo
+RET=0
+printf "trying %s with correct password\n" "${ACCTA}"
+su - "${ACCTA}" -c "${COMMONDIR}/asuser ${PASSWD}" || RET=$?
+printf "%s with correct password, return value %s\n" "${ACCTA}" "${RET}"
+if [ "$(cat ${HOMEDIRA}/stdout)" != "0" ]; then
+ printf >&2 "id -u did not give 0\n"
+ printf >&2 "stdout:\n"
+ cat >&2 ${HOMEDIRA}/stdout
+ printf >&2 "stderr:\n"
+ cat >&2 ${HOMEDIRA}/stderr
+ printf >&2 "exit code %s\n" "${RET}"
+ printf >&2 "exit 1\n" "${RET}"
+ exit 1
+fi
+
+printf "========= test %s\.2: account group member, wrong password\n" "${TESTNR}"
+rm -f "${HOMEDIRA}/std*"
+RET=0
+printf "trying %s with wrong password\n" "${ACCTA}"
+su - "${ACCTA}" -c "${COMMONDIR}/asuser wrongpasswd" || RET=$?
+printf "%s with wrong password, return value %s\n" "${ACCTA}" "${RET}"
+head -n-0 ${HOMEDIRA}/stdout ${HOMEDIRA}/stderr
+printf -- "\n-------\n"
+for string in "[sudo] password for ${ACCTA}" "Sorry, try again" "sudo: no password was provided" "sudo: 1 incorrect password attempt"; do
+ if ! grep -F "${string}" ${HOMEDIRA}/stderr; then
+ printf "%s missing in stderr output\n" "${string}"
+ printf >&2 "stdout:\n"
+ cat >&2 ${HOMEDIRA}/stdout
+ printf >&2 "stderr:\n"
+ cat >&2 ${HOMEDIRA}/stderr
+ printf >&2 "\nexit code %s\n" "${RET}"
+ printf >&2 -- "------\n exit 1\n"
+ exit 1
+ fi
+done
+
+printf "========= test %s\.3: account not group member, correct password\n" "${TESTNR}"
+deluser ${ACCTB} 2>/dev/null || true
+adduser --disabled-password --home "${HOMEDIRB}" --gecos "" "${ACCTB}"
+printf "%s:%s\n" "${ACCTB}" "${PASSWD}" | chpasswd
+RET=0
+printf "trying %s (no sudo membership) with correct password\n" "${ACCTB}"
+su - "${ACCTB}" -c "${COMMONDIR}/asuser ${PASSWD}" || RET=$?
+printf "%s with correct password, return value %s\n" "${ACCTB}" "${RET}"
+head -n-0 ${HOMEDIRB}/stdout ${HOMEDIRB}/stderr
+printf -- "\n-------\n"
+for string in "[sudo] password for ${ACCTB}" "${ACCTB} is not allowed to run sudo on"; do
+ if ! grep -F "${string}" ${HOMEDIRB}/stderr; then
+ printf "%s missing in stderr output\n" "${string}"
+ printf >&2 "stdout:\n"
+ cat >&2 ${HOMEDIRB}/stdout
+ printf >&2 "stderr:\n"
+ cat >&2 ${HOMEDIRB}/stderr
+ printf >&2 "\nexit code %s\n" "${RET}"
+ printf >&2 -- "------\n exit 1\n"
+ exit 1
+ fi
+done
+
+printf "test series sucessful, exit 0\n"
+exit 0
+
diff --git a/debian/tests/03/ldif/container.ldif b/debian/tests/03/ldif/container.ldif
new file mode 100644
index 0000000..8f02a68
--- /dev/null
+++ b/debian/tests/03/ldif/container.ldif
@@ -0,0 +1,5 @@
+dn: ou=SUDOers,dc=example,dc=com
+objectClass: top
+objectClass: organizationalUnit
+ou: SUDOers
+
diff --git a/debian/tests/03/ldif/debconf b/debian/tests/03/ldif/debconf
new file mode 100644
index 0000000..d40ae8c
--- /dev/null
+++ b/debian/tests/03/ldif/debconf
@@ -0,0 +1,16 @@
+slapd slapd/password1 password ldappw
+slapd slapd/password2 password ldappw
+slapd slapd/internal/adminpw password ldappw
+slapd slapd/internal/generated_adminpw password ldappw
+slapd slapd/password_mismatch note
+slapd slapd/domain string example.com
+slapd slapd/dump_database_destdir string /var/backups/slapd-VERSION
+slapd slapd/purge_database boolean true
+slapd slapd/dump_database select when needed
+slapd slapd/no_configuration boolean false
+slapd slapd/ppolicy_schema_needs_update select abort installation
+slapd slapd/invalid_config boolean false
+slapd shared/organization string example.com
+slapd slapd/move_old_database boolean true
+slapd slapd/unsafe_selfwrite_acl note
+
diff --git a/debian/tests/03/ldif/sudoers.ldif b/debian/tests/03/ldif/sudoers.ldif
new file mode 100644
index 0000000..d321d52
--- /dev/null
+++ b/debian/tests/03/ldif/sudoers.ldif
@@ -0,0 +1,32 @@
+dn: cn=defaults,ou=SUDOers,dc=example,dc=com
+objectClass: top
+objectClass: sudoRole
+cn: defaults
+description: Default sudoOption's go here
+sudoOption: env_reset
+sudoOption: mail_badpass
+sudoOption: secure_path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
+sudoOption: use_pty
+
+dn: cn=root,ou=SUDOers,dc=example,dc=com
+objectClass: top
+objectClass: sudoRole
+cn: root
+sudoUser: root
+sudoHost: ALL
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoCommand: ALL
+sudoOrder: 1
+
+dn: cn=%sudo,ou=SUDOers,dc=example,dc=com
+objectClass: top
+objectClass: sudoRole
+cn: %sudo
+sudoUser: %sudo
+sudoHost: ALL
+sudoRunAsUser: ALL
+sudoRunAsGroup: ALL
+sudoCommand: ALL
+sudoOrder: 2
+
diff --git a/debian/tests/04-getroot-sssd b/debian/tests/04-getroot-sssd
new file mode 100755
index 0000000..eb13852
--- /dev/null
+++ b/debian/tests/04-getroot-sssd
@@ -0,0 +1,138 @@
+#!/bin/sh
+
+set -e
+
+# DEBIAN_FRONTEND=noninteractive apt --yes install adduser slapd ldap-utils sssd cron sudo man-db procps vim whiptail
+# slappasswd -s kkkk
+
+TESTNR="04"
+BASEDIR="$(pwd)/debian/tests"
+COMMONDIR="${BASEDIR}/common"
+DIR="${BASEDIR}/${TESTNR}"
+PATH="/bin:/usr/bin:/sbin:/usr/sbin"
+ACCTA="testuser1"
+ACCTB="testuser2"
+PASSWD="test${TESTNR}23456"
+HOMEDIRA="/home/${ACCTA}"
+HOMEDIRB="/home/${ACCTB}"
+LDIFDIR="${DIR}/ldif"
+SSSDCONF="/etc/sssd/sssd.conf"
+
+trap '
+ kill $(pidof slapd) 2>/dev/null || true
+ kill $(pidof sssd) 2>/dev/null || true
+' 0 INT QUIT ABRT PIPE TERM
+
+# openssl req -x509 -days 365 -nodes -newkey rsa:4096 -keyout server_key.pem -out server_cert.pem --subj "/C=DE/CN=emptysid86.zugschlus.de"
+
+< ${LDIFDIR}/debconf debconf-set-selections
+printf "clean up ldap database ... "
+rm -rf /var/lib/ldap/*.mdb
+printf "move configuration in place ... "
+mkdir -p /etc/ldap /etc/sssd
+cp ${LDIFDIR}/server_*.pem /etc/ldap/
+cp ${LDIFDIR}/ldap.conf /etc/ldap/
+chown openldap:openldap /etc/ldap/server_*.pem
+chmod 600 /etc/ldap/server_key.pem
+cp ${LDIFDIR}/sssd.conf /etc/sssd
+chown root:root /etc/sssd/sssd.conf
+chmod 600 /etc/sssd/sssd.conf
+cp ${LDIFDIR}/slapd-default /etc/default/slapd
+echo "slapd: [::1]" >> /etc/hosts.allow
+printf "reconfigure slapd ... "
+DEBIAN_FRONTEND=noninteractive dpkg-reconfigure -pcritical slapd 2>/dev/null
+kill $(pidof slapd) 2>/dev/null || true
+sleep 1
+printf "start slapd ... "
+slapd -h "ldaps:/// ldapi:///" -g openldap -u openldap -F /etc/ldap/slapd.d
+# ldapsearch -x -LLL -s base -b "" namingContexts should work here
+printf "set LDAP passwords"
+ldapmodify -v -Y external -H ldapi:/// -f ${LDIFDIR}/tls.ldif 2>/dev/null
+printf "set LDAP passwords for admin"
+ldapmodify -v -Y external -H ldapi:/// -f ${LDIFDIR}/adminpw.ldif 2>/dev/null
+printf "set LDAP passwords for admin example"
+ldapmodify -v -Y external -H ldapi:/// -f ${LDIFDIR}/adminpw-example-com.ldif 2>/dev/null
+printf "add users and groups OUs ..."
+ldapadd -x -D "cn=admin,dc=example,dc=com" -w ldappw -f ${LDIFDIR}/sss-ous.ldif 2>/dev/null
+printf "add users ..."
+
+printf "sssd.conf ...\n"
+cp ${LDIFDIR}/sssd.conf "${SSSDCONF}"
+
+printf "sudoers file ...\n"A
+mkdir -p /etc/sudoers.d/
+mv ${LDIFDIR}/ldapsudoers /etc/sudoers.d/
+chown root:root "${SSSDCONF}" /etc/sudoers.d/ /etc/sudoers.d/*
+chmod 755 /etc/sudoers.d/
+chmod 600 "${SSSDCONF}" /etc/sudoers.d/*
+kill $(pidof sssd) 2>/dev/null || true
+sleep 1
+sssd --logger=files -D
+
+for user in testuser1 testuser2; do
+ ldapadd -x -D "cn=admin,dc=example,dc=com" -w ldappw -f ${LDIFDIR}/${user}.ldif 2>/dev/null
+ mkdir -p /home/${user}
+ chown ${user}:nogroup /home/${user}
+done
+ldapadd -x -D "cn=admin,dc=example,dc=com" -w ldappw -f ${LDIFDIR}/ldapsudoers.ldif 2>/dev/null
+# ldapsearch -x -D "cn=admin,dc=example,dc=com" -w ldappw -b "dc=example,dc=com" -s sub "(objectclass=*)" should work here.
+
+printf "========= test %s\.1: account group member, correct password\n" "${TESTNR}"
+RET=0
+printf "trying %s with correct password\n" "${ACCTA}"
+su - "${ACCTA}" -c "${COMMONDIR}/asuser ${PASSWD}" || RET=$?
+printf "%s with correct password, return value %s\n" "${ACCTA}" "${RET}"
+if [ "$(cat ${HOMEDIRA}/stdout)" != "0" ]; then
+ printf >&2 "id -u did not give 0\n"
+ printf >&2 "stdout:\n"
+ cat >&2 ${HOMEDIRA}/stdout
+ printf >&2 "stderr:\n"
+ cat >&2 ${HOMEDIRA}/stderr
+ printf >&2 "exit code %s\n" "${RET}"
+ printf >&2 "exit 1\n" "${RET}"
+ exit 1
+fi
+
+printf "========= test %s\.2: account group member, wrong password\n" "${TESTNR}"
+rm -f "${HOMEDIRA}/std*"
+RET=0
+printf "trying %s with wrong password\n" "${ACCTA}"
+su - "${ACCTA}" -c "${COMMONDIR}/asuser wrongpasswd" || RET=$?
+printf "%s with wrong password, return value %s\n" "${ACCTA}" "${RET}"
+head -n-0 ${HOMEDIRA}/stdout ${HOMEDIRA}/stderr
+printf -- "\n-------\n"
+for string in "[sudo] password for ${ACCTA}" "Sorry, try again" "sudo: no password was provided" "sudo: 1 incorrect password attempt"; do
+ if ! grep -F "${string}" ${HOMEDIRA}/stderr; then
+ printf "%s missing in stderr output\n" "${string}"
+ printf >&2 "stdout:\n"
+ cat >&2 ${HOMEDIRA}/stdout
+ printf >&2 "stderr:\n"
+ cat >&2 ${HOMEDIRA}/stderr
+ printf >&2 "\nexit code %s\n" "${RET}"
+ printf >&2 -- "------\n exit 1\n"
+ exit 1
+ fi
+done
+
+printf "========= test %s\.3: account not group member, correct password\n" "${TESTNR}"
+printf "trying %s (no sudo membership) with correct password\n" "${ACCTB}"
+su - "${ACCTB}" -c "${COMMONDIR}/asuser ${PASSWD}" || RET=$?
+printf "%s with correct password, return value %s\n" "${ACCTB}" "${RET}"
+head -n-0 ${HOMEDIRB}/stdout ${HOMEDIRB}/stderr
+printf -- "\n-------\n"
+for string in "[sudo] password for ${ACCTB}: ${ACCTB} is not in the sudoers file." ; do
+ if ! grep -q -F "${string}" ${HOMEDIRB}/stderr; then
+ printf "%s missing in stderr output\n" "${string}"
+ printf >&2 "stdout:\n"
+ cat >&2 ${HOMEDIRB}/stdout
+ printf >&2 "stderr:\n"
+ cat >&2 ${HOMEDIRB}/stderr
+ printf >&2 "\nexit code %s\n" "${RET}"
+ printf >&2 -- "------\n exit 1\n"
+ exit 1
+ fi
+done
+
+printf "test series sucessful, exit 0\n"
+exit 0
+
diff --git a/debian/tests/04/ldif/adminpw-example-com.ldif b/debian/tests/04/ldif/adminpw-example-com.ldif
new file mode 100644
index 0000000..adf42d5
--- /dev/null
+++ b/debian/tests/04/ldif/adminpw-example-com.ldif
@@ -0,0 +1,4 @@
+dn: olcDatabase={1}mdb,cn=config
+changetype: modify
+replace: olcRootPW
+olcRootPW: {SSHA}5VEuBX9dLCSCj+TIp7XBXQRb3F5M2aSN
diff --git a/debian/tests/04/ldif/adminpw.ldif b/debian/tests/04/ldif/adminpw.ldif
new file mode 100644
index 0000000..6cf1bb8
--- /dev/null
+++ b/debian/tests/04/ldif/adminpw.ldif
@@ -0,0 +1,7 @@
+# this sets a password ldappw for the config database
+# ldapsearch -H ldapi:// -LLL -D "cn=admin,cn=config" -W -b "cn=config" "(olcRootDN=*)" dn olcRootDN olcRootPW olcSuffix
+# should work without -Y EXTERNAL and as normal user now
+dn: olcDatabase={0}config,cn=config
+changetype: modify
+replace: olcRootPW
+olcRootPW: {SSHA}5VEuBX9dLCSCj+TIp7XBXQRb3F5M2aSN
diff --git a/debian/tests/04/ldif/container.ldif b/debian/tests/04/ldif/container.ldif
new file mode 100644
index 0000000..8f02a68
--- /dev/null
+++ b/debian/tests/04/ldif/container.ldif
@@ -0,0 +1,5 @@
+dn: ou=SUDOers,dc=example,dc=com
+objectClass: top
+objectClass: organizationalUnit
+ou: SUDOers
+
diff --git a/debian/tests/04/ldif/debconf b/debian/tests/04/ldif/debconf
new file mode 100644
index 0000000..bb14313
--- /dev/null
+++ b/debian/tests/04/ldif/debconf
@@ -0,0 +1,15 @@
+slapd slapd/password1 password ldappw
+slapd slapd/password2 password ldappw
+slapd slapd/internal/adminpw password ldappw
+slapd slapd/internal/generated_adminpw password ldappw
+slapd slapd/password_mismatch note
+slapd slapd/domain string example.com
+slapd slapd/dump_database_destdir string /var/backups/slapd-VERSION
+slapd slapd/purge_database boolean true
+slapd slapd/no_configuration boolean false
+slapd slapd/ppolicy_schema_needs_update select abort installation
+slapd slapd/invalid_config boolean false
+slapd shared/organization string example.com
+slapd slapd/move_old_database boolean true
+slapd slapd/unsafe_selfwrite_acl note
+
diff --git a/debian/tests/04/ldif/ldap.conf b/debian/tests/04/ldif/ldap.conf
new file mode 100644
index 0000000..3f3000a
--- /dev/null
+++ b/debian/tests/04/ldif/ldap.conf
@@ -0,0 +1,6 @@
+BASE dc=example,dc=com
+URI ldaps://[::1]:636/
+TLS_CACERT /etc/ldap/server_cert.pem
+TLS_REQCERT allow
+SASL_NOCANON on
+
diff --git a/debian/tests/04/ldif/ldapsudoers b/debian/tests/04/ldif/ldapsudoers
new file mode 100644
index 0000000..8d11b0b
--- /dev/null
+++ b/debian/tests/04/ldif/ldapsudoers
@@ -0,0 +1 @@
+%ldapsudoers ALL=(ALL:ALL) ALL
diff --git a/debian/tests/04/ldif/ldapsudoers.ldif b/debian/tests/04/ldif/ldapsudoers.ldif
new file mode 100644
index 0000000..029d73e
--- /dev/null
+++ b/debian/tests/04/ldif/ldapsudoers.ldif
@@ -0,0 +1,6 @@
+dn: cn=ldapsudoers,ou=groups,dc=example,dc=com
+objectClass: posixGroup
+objectClass: top
+gidNumber: 270
+cn: ldapsudoers
+memberUid: testuser1
diff --git a/debian/tests/04/ldif/server_cert.pem b/debian/tests/04/ldif/server_cert.pem
new file mode 100644
index 0000000..69392cd
--- /dev/null
+++ b/debian/tests/04/ldif/server_cert.pem
@@ -0,0 +1,30 @@
+-----BEGIN CERTIFICATE-----
+MIIFMTCCAxmgAwIBAgIUatkSzjnbPNHqrbv9GByfPIoUjtYwDQYJKoZIhvcNAQEL
+BQAwKDELMAkGA1UEBhMCREUxGTAXBgNVBAMMEGxkYXAuZXhhbXBsZS5jb20wHhcN
+MjMwMTAyMTc0NDA2WhcNMjQwMTAyMTc0NDA2WjAoMQswCQYDVQQGEwJERTEZMBcG
+A1UEAwwQbGRhcC5leGFtcGxlLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
+AgoCggIBAOscbfVg0NKHrFWLv2y+veqaRv/8ANup0ZSm/Qyx1zHdCV0sQMxfxeVb
+OMcucCoBbAsPznHLZXaJFL3cgqdcaQ5oLYGCaaj7TbfBwm4i0bGP+xpDV7nvxyW3
+HLw5mYmoYpm5iAFaRuqWuMbCU2bILuTVO/D7V/1TUS4ciLpz9Dw5rrFy9t+ZURMv
+bf45/tjlD4T6ItDrr4gBKJ6fqRbCVZl38oyiont/Spm+nBRpHpZz70F4AYo8rwMD
+dLGonJ85KrVeIDg5TZEMEKgxgXu6hrvNVxyGWXmA3mOVy+vyRj8XHDebDX8qmPgF
+g/Rzzm4VgrlXqtuEc/YQqyu6VqpNR9Yu0oj+q7J/A4BU316PioNB4zHWWwqqBEKu
+bXy9EtXfYXppPV56/XfnYm6mbyIn0x382oBrcQiQD5pTWoz61lawrt9YDGnDvWSH
+BHUhzoVSY++D0QX0hae35zZkTbW9/eXpZGr5UDVFgkZGWDPPxrXyOAgiJfwiTtqm
+Du9Lp3JycX95ywGhTPBNM9nvaPk5bBSWgz9uaoP2NY4VQga4vhn2mC0WbJOtUHSm
++tMpjTcBIJzpdyH0yh7DEGORk5aev9gU+K1VcSRD/3pXkSjo7xSEfSNW+flAGwVS
+UABDs/0XkdmhvL4zawnuMapEttWqHKH0wrQLkvzTkFUnqJsQ8cerAgMBAAGjUzBR
+MB0GA1UdDgQWBBS1r+sdVFP2hBByMEw9iSvkvvGqxTAfBgNVHSMEGDAWgBS1r+sd
+VFP2hBByMEw9iSvkvvGqxTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA
+A4ICAQA8otnqTtetl0Tqqx+lNsmfOi2iEbptyKuvDhSBSlkdHVGD+rRilDeehdVN
+9vE2fNOdYdtAfxBVEUW6S4RRY3gJZ38oik0JbYxotUYqAgFzY53Zg5CAQpmGDCYg
+GMS/2zHlo5ZFNoKLMJG5o8qGao1HehBlIJ9D06mRQO88aguMa4jPBYHMb43ZWOxh
+Un9P6fOl7bfRqomxgixnovPlFiELg/ZWANpECRY7lsVahKLndWf+Tw3Ayp4+CpvL
+mWc0xRCYTFDua1lyLypxsH/4H5IZlDwpw8bvSAmmpdqhbA4Sh+Qo6gXn4Bm92A4L
+sltnUjCliJb79Q3gkuvIB/qlPPbZ/s9L0OxRHnHYR+7JfVxlsWb2guMApGc4R3Um
+5U4sK4QEFZFCBgsrA3DpXQo1pW30DCZjXjrzQ3kbPuKX8njOzPI9Q02xdoMkuqMw
+o4tvo28xgWlW2HZrzU7fnm7t0MTGJG33LKlcz/tRco9Ky+YxKz5HvQAGCKrb3L6x
+iOeVuT90cKfNX7pVoHNR7YSav+n9YacIknB+HBpGLKGlfvHIlwvCMtOK9axHxUiO
+AZaCYYUXgFbYetyoux5PyYBDwIrJSIw7FpQkONmHLRSM2j3S9RRGi9ipR3jzvvqz
+d7dsFok749nOEuJ4qvnWrJ5WkcrbrX5GcR0UL1mWSJqCRXOp1A==
+-----END CERTIFICATE-----
diff --git a/debian/tests/04/ldif/server_key.pem b/debian/tests/04/ldif/server_key.pem
new file mode 100644
index 0000000..7baef03
--- /dev/null
+++ b/debian/tests/04/ldif/server_key.pem
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDrHG31YNDSh6xV
+i79svr3qmkb//ADbqdGUpv0Msdcx3QldLEDMX8XlWzjHLnAqAWwLD85xy2V2iRS9
+3IKnXGkOaC2Bgmmo+023wcJuItGxj/saQ1e578cltxy8OZmJqGKZuYgBWkbqlrjG
+wlNmyC7k1Tvw+1f9U1EuHIi6c/Q8Oa6xcvbfmVETL23+Of7Y5Q+E+iLQ66+IASie
+n6kWwlWZd/KMoqJ7f0qZvpwUaR6Wc+9BeAGKPK8DA3SxqJyfOSq1XiA4OU2RDBCo
+MYF7uoa7zVcchll5gN5jlcvr8kY/Fxw3mw1/Kpj4BYP0c85uFYK5V6rbhHP2EKsr
+ulaqTUfWLtKI/quyfwOAVN9ej4qDQeMx1lsKqgRCrm18vRLV32F6aT1eev1352Ju
+pm8iJ9Md/NqAa3EIkA+aU1qM+tZWsK7fWAxpw71khwR1Ic6FUmPvg9EF9IWnt+c2
+ZE21vf3l6WRq+VA1RYJGRlgzz8a18jgIIiX8Ik7apg7vS6dycnF/ecsBoUzwTTPZ
+72j5OWwUloM/bmqD9jWOFUIGuL4Z9pgtFmyTrVB0pvrTKY03ASCc6Xch9MoewxBj
+kZOWnr/YFPitVXEkQ/96V5Eo6O8UhH0jVvn5QBsFUlAAQ7P9F5HZoby+M2sJ7jGq
+RLbVqhyh9MK0C5L805BVJ6ibEPHHqwIDAQABAoICAE2uU4BnECf3Ts/nAAT4krxQ
+ZBQRGeF6HvaMJADNQ6pEe2MPC4vbOwIYXU6mP8YJOT8AZnf/uZLsIO/IS1zrsgRi
+FGL9iVadTaTgvpJwK7OMvG0Fghc7q6OA+FwSdfHfMlDTVaYIw3Sf/wYgz7iefKv7
+7jWlfgGDxUdEg0KDrFc3wcn8j6f6Oqjpm2CLnfHg4PtRQC6iKJl5tIeQfig4Zlry
+IDAqTiAawzXAHka6IrKYNJ1/fpbDjRmkSyql6LXNCBjrtB6PhFrfzyMbVEpiq0Ci
+zFzu4OI923yw0jMvldkjlB2lO9Tf6LHN9LbQioyhy9LcLeYgwcWz5TJp+1eCeDCg
+np5ipwqhkTvx9T6rQRtInZCJZSmY+JxWYlQJ7Gz2e4V4L+9or3nTBs/YDPV+dDSs
+SjcQgEstc/nEj0y4l2iEZq7N9Ro3PtWM6beM3yYacsJEdDwhH2vRBj/xl9j3fKc+
+0kvWem0r9+kKXw/LweSmeTTtrsjKZPi2pFrvXBG1yrhwmERtQOoQN0llRgQy7XBW
+EUN3WMHYVfUcKzRRHrlDQ3tTuTlm1cFv6JQ5ip4sedNJSkWMBAv1yyLH5CnISm6k
+OpOhz1oGHTNG91PkVvVJP8GvhOXafi84bLrXU7FJaAkgci/EGQAkqO5R3ITjYKMG
+eoPul58iQ8057C9As9LhAoIBAQD4mvuPSxTwaH/9AsEPrv3fhmG0QfeD0wFUvFKO
+X/gDfVbkQjH6CcNe5QjbRzooJAGdENmQzn8S9qhqcdghYKAtKnabwhgqzVv2Xr6z
+XpyhmJCF+MEaTfhIw/C1HmjURwdxmk0w4uaTOixKlCwwA1bi69dDZ82dMqM1Y7u4
+uPQwykud4AAeFRETAcWAXe0BZ4d5uow7siaSRS24Do7SEAa7zcLiTqVbuKhBNqRa
+FSY/r7f8W78oL7Z/TwhYP0MpQLAG9gAUc48BO6Rm5tJfMmd2D8KLQ2Lfze4ETBSA
+ZJk0j1LuXNWzSM2wQ4vbhGrw4qLTue6uv9V0lY1FB0d9y+JLAoIBAQDyGrGLPPeR
+IBHzXiFGGFd/it20ux1x7+iFhC/NEwJVKU6oVO39jqte4nVfFo5cb4WKuQHfmiEN
+E6hcdkXBCezgTGKsvqaY+nmmoNMNg2wh/cGc6VoBMiixZYa43S+i5U4pdWZbwbgB
+1zUqh1k1NcSBQErqoML2R1aORw627OV1Ef+/UpnVlQGlrqor+w1XtmOb9s6/02gb
+QA+pZlLEuyJwhXhxAioFoY+G7zKcJisAKORGS7ZtvmCzOqq2cUD4EYtYPGJmjpU5
+yfwW7YoJALmoIckORHQuQXkL6nnDXOhvL66dKAU523NkbfHUmdl/DyiedZxOtUH8
+Jky+oarQm1QhAoIBAQCNDWItqyv2O1Ri+W0QuPjSGizVWZhV8yKOMUul/E17rWHf
+oK86bs+qx8h+oasdm1BPDYBj6MWwvMJRosY+KdS3y6AAP9/2aQ4Eez04CDZWeXmG
+id0GT7bPklzAZsCTsLlIe4PQeOzaG+eFaQypMTvbBHTeicbfqhtv72ZTKJ1kEWNV
+8AIhD1LgteCZNLGEWnlDV9S5ChtYYmfORnRCO1WWuOgZ/wVTRTIxzg7yDY3mFI0P
+Yf7Tjj69fNn/N+WjQlCdonXpJKe+y1g8CjrSSIbrNYXr/g/ba7vgNEptjqZea/Nh
+ysp1LpmFqM1xf3AtvGkmOBh0jeNOgovk3nxxo3yBAoIBAGs6/XYhS7mAjdLP10b3
+kxGPjQD2e2UykDdKw+09xSO5BvixnTNX1HlTLg8uq2Evl+NIbBcAajEjisdhLyX/
+4mW6D15ZlupczjLKOpBarDMl9HIuPMoY0EM6J4CLnwS0MXlVYT+0vm46RncOualC
+pkVlF4lyKMfx8tlTiaXlqP/AOBkiWbZqp+8dPIv8Rv2Zb+btWsdFuG+RYR5zjqdK
+B0f1JdJP1hLmau6l1TGqChOpCOpFsIhM8QGRM3lZEiCNjL1JCYBJGLkeyEPTc/bm
+1lQsmqNyGE9Aen+Xm9S2utA8O0eqKR5mH2bU925lshp/uUrt5oxJ5e7re8RXUJPS
+qGECggEAMBcRhHnk9mlo6zi89hRY4YduN14ahxatZu99fFep9Ea3mslcTDzy26Xm
+Mw0X3oij6+eJODlWpwzUMp5MylI8XEeOkfZ9il+6etFSOK6QWe2U7SDAy6nXYUVB
+PZc5kTtCYSMIUmU+GjShMoEYPNCjqRSEY9sArZ85wFWEl5nRn5sEg8NLBhbURWu1
+iY1R0ie8XeXEoOWujMfhVmJUNadkeR23/XMmzfZ6M5gavkYkUjNMvCNMu7+GVeYU
+uuxNmnNqjJP5GcLsd7dgzgslE+FPPxHiVjONIR7qrZwZcg9rGO2ODrLnuHZHzZha
+x4rwQL3+5SADD++19sqJhDoXJW8KEw==
+-----END PRIVATE KEY-----
diff --git a/debian/tests/04/ldif/slapd-default b/debian/tests/04/ldif/slapd-default
new file mode 100644
index 0000000..9d92858
--- /dev/null
+++ b/debian/tests/04/ldif/slapd-default
@@ -0,0 +1,7 @@
+SLAPD_CONF=
+SLAPD_USER="openldap"
+SLAPD_GROUP="openldap"
+SLAPD_PIDFILE=
+SLAPD_SERVICES="ldaps:/// ldapi:///"
+SLAPD_SENTINEL_FILE=/etc/ldap/noslapd
+SLAPD_OPTIONS=""
diff --git a/debian/tests/04/ldif/sss-ous.ldif b/debian/tests/04/ldif/sss-ous.ldif
new file mode 100644
index 0000000..5ba018c
--- /dev/null
+++ b/debian/tests/04/ldif/sss-ous.ldif
@@ -0,0 +1,9 @@
+dn: ou=users,dc=example,dc=com
+objectClass: top
+objectClass: organizationalUnit
+ou: users
+
+dn: ou=groups,dc=example,dc=com
+objectClass: top
+objectClass: organizationalUnit
+ou: groups
diff --git a/debian/tests/04/ldif/sssd.conf b/debian/tests/04/ldif/sssd.conf
new file mode 100755
index 0000000..ee06ef5
--- /dev/null
+++ b/debian/tests/04/ldif/sssd.conf
@@ -0,0 +1,24 @@
+[sssd]
+domains = example.com
+services = nss, pam
+debug_level = 0x01ff
+
+[domain/example.com]
+id_provider = ldap
+auth_provider = ldap
+
+ldap_uri = ldaps://[::1]:636/
+ldap_search_base = dc=example,dc=com
+
+ldap_tls_cacert = /etc/ldap/server_cert.pem
+ldap_tls_reqcert = allow
+
+ldap_default_bind_dn = cn=admin,dc=example,dc=com
+ldap_default_authtok_type = password
+ldap_default_authtok = ldappw
+
+[pam]
+offline_credentials_expiration = 2
+offline_failed_login_attempts = 3
+offline_failed_login_delay = 5
+
diff --git a/debian/tests/04/ldif/testuser1.ldif b/debian/tests/04/ldif/testuser1.ldif
new file mode 100644
index 0000000..2419a68
--- /dev/null
+++ b/debian/tests/04/ldif/testuser1.ldif
@@ -0,0 +1,16 @@
+dn: uid=testuser1,ou=users,dc=example,dc=com
+objectClass: top
+objectClass: account
+objectClass: posixAccount
+objectClass: shadowAccount
+cn: testuser1
+uid: testuser1
+uidNumber: 10001
+gidNumber: 100
+homeDirectory: /home/testuser1
+loginShell: /bin/bash
+gecos: testuser1 from LDAP
+userPassword: {SSHA}n8CrO1tNcRrd4u8rMLOE91a18iFRQFBx
+shadowLastChange: 0
+shadowMax: 0
+shadowWarning: 0
diff --git a/debian/tests/04/ldif/testuser2.ldif b/debian/tests/04/ldif/testuser2.ldif
new file mode 100644
index 0000000..541c383
--- /dev/null
+++ b/debian/tests/04/ldif/testuser2.ldif
@@ -0,0 +1,17 @@
+dn: uid=testuser2,ou=users,dc=example,dc=com
+objectClass: top
+objectClass: account
+objectClass: posixAccount
+objectClass: shadowAccount
+cn: testuser2
+uid: testuser2
+uidNumber: 10002
+gidNumber: 100
+homeDirectory: /home/testuser2
+loginShell: /bin/bash
+gecos: testuser2 from LDAP
+userPassword: {SSHA}n8CrO1tNcRrd4u8rMLOE91a18iFRQFBx
+shadowLastChange: 0
+shadowMax: 0
+shadowWarning: 0
+
diff --git a/debian/tests/04/ldif/tls.ldif b/debian/tests/04/ldif/tls.ldif
new file mode 100644
index 0000000..012adf2
--- /dev/null
+++ b/debian/tests/04/ldif/tls.ldif
@@ -0,0 +1,10 @@
+dn: cn=config
+changetype: modify
+add: olcTLSCACertificateFile
+olcTLSCACertificateFile: /etc/ldap/server_cert.pem
+-
+add: olcTLSCertificateKeyFile
+olcTLSCertificateKeyFile: /etc/ldap/server_key.pem
+-
+add: olcTLSCertificateFile
+olcTLSCertificateFile: /etc/ldap/server_cert.pem
diff --git a/debian/tests/common/asuser b/debian/tests/common/asuser
new file mode 100755
index 0000000..291b40a
--- /dev/null
+++ b/debian/tests/common/asuser
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+set -e
+
+echo "${1:-}" | sudo -u "${2:-root}" --stdin id -u > "${3:-stdout}" 2> "${4:-stderr}"
+
+
diff --git a/debian/tests/control b/debian/tests/control
new file mode 100644
index 0000000..a0bc0d6
--- /dev/null
+++ b/debian/tests/control
@@ -0,0 +1,16 @@
+Tests: 01-getroot
+Depends: sudo, adduser
+Restrictions: needs-root
+
+Tests: 02-1003969-audit-no-resolve
+Depends: sudo
+Restrictions: needs-root
+
+Tests: 03-getroot-ldap
+Depends: sudo-ldap, adduser, slapd, ldap-utils, cron
+Restrictions: needs-root
+
+#Tests: 04-getroot-sssd
+#Depends: sudo, adduser, slapd, ldap-utils, sssd-common, sssd-ldap, cron
+#Restrictions: needs-root
+