1%{
2/*
3 * Copyright (c) 1996, 1998-2005, 2007-2010
4 *	Todd C. Miller <Todd.Miller@courtesan.com>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
18 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
19 *
20 * Sponsored in part by the Defense Advanced Research Projects
21 * Agency (DARPA) and Air Force Research Laboratory, Air Force
22 * Materiel Command, USAF, under agreement number F39502-99-1-0512.
23 */
24
25#include <config.h>
26
27#include <sys/types.h>
28#include <sys/param.h>
29#include <stdio.h>
30#ifdef STDC_HEADERS
31# include <stdlib.h>
32# include <stddef.h>
33#else
34# ifdef HAVE_STDLIB_H
35#  include <stdlib.h>
36# endif
37#endif /* STDC_HEADERS */
38#ifdef HAVE_STRING_H
39# include <string.h>
40#endif /* HAVE_STRING_H */
41#ifdef HAVE_STRINGS_H
42# include <strings.h>
43#endif /* HAVE_STRINGS_H */
44#ifdef HAVE_UNISTD_H
45# include <unistd.h>
46#endif /* HAVE_UNISTD_H */
47#if defined(YYBISON) && defined(HAVE_ALLOCA_H) && !defined(__GNUC__)
48# include <alloca.h>
49#endif /* YYBISON && HAVE_ALLOCA_H && !__GNUC__ */
50#include <limits.h>
51
52#include "sudo.h"
53#include "parse.h"
54#include "gram.h"
55
56/*
57 * We must define SIZE_MAX for yacc's skeleton.c.
58 * If there is no SIZE_MAX or SIZE_T_MAX we have to assume that size_t
59 * could be signed (as it is on SunOS 4.x).
60 */
61#ifndef SIZE_MAX
62# ifdef SIZE_T_MAX
63#  define SIZE_MAX	SIZE_T_MAX
64# else
65#  define SIZE_MAX	INT_MAX
66# endif /* SIZE_T_MAX */
67#endif /* SIZE_MAX */
68
69/*
70 * Globals
71 */
72extern int sudolineno;
73extern int last_token;
74extern char *sudoers;
75int sudoers_warnings = TRUE;
76int parse_error = FALSE;
77int pedantic = FALSE;
78int errorlineno = -1;
79char *errorfile = NULL;
80
81struct defaults_list defaults;
82struct userspec_list userspecs;
83
84/*
85 * Local protoypes
86 */
87static void  add_defaults	__P((int, struct member *, struct defaults *));
88static void  add_userspec	__P((struct member *, struct privilege *));
89static struct defaults *new_default __P((char *, char *, int));
90static struct member *new_member __P((char *, int));
91       void  yyerror		__P((const char *));
92
93void
94yyerror(s)
95    const char *s;
96{
97    /* If we last saw a newline the error is on the preceding line. */
98    if (last_token == COMMENT)
99	sudolineno--;
100
101    /* Save the line the first error occurred on. */
102    if (errorlineno == -1) {
103	errorlineno = sudolineno;
104	errorfile = estrdup(sudoers);
105    }
106    if (sudoers_warnings && s != NULL) {
107#ifndef TRACELEXER
108	(void) fprintf(stderr, ">>> %s: %s near line %d <<<\n", sudoers, s,
109	    sudolineno);
110#else
111	(void) fprintf(stderr, "<*> ");
112#endif
113    }
114    parse_error = TRUE;
115}
116%}
117
118%union {
119    struct cmndspec *cmndspec;
120    struct defaults *defaults;
121    struct member *member;
122    struct runascontainer *runas;
123    struct privilege *privilege;
124    struct sudo_command command;
125    struct cmndtag tag;
126    struct selinux_info seinfo;
127    char *string;
128    int tok;
129}
130
131%start file				/* special start symbol */
132%token <command> COMMAND		/* absolute pathname w/ optional args */
133%token <string>  ALIAS			/* an UPPERCASE alias name */
134%token <string>	 DEFVAR			/* a Defaults variable name */
135%token <string>  NTWKADDR		/* ipv4 or ipv6 address */
136%token <string>  NETGROUP		/* a netgroup (+NAME) */
137%token <string>  USERGROUP		/* a usergroup (%NAME) */
138%token <string>  WORD			/* a word */
139%token <tok>	 DEFAULTS		/* Defaults entry */
140%token <tok>	 DEFAULTS_HOST		/* Host-specific defaults entry */
141%token <tok>	 DEFAULTS_USER		/* User-specific defaults entry */
142%token <tok>	 DEFAULTS_RUNAS		/* Runas-specific defaults entry */
143%token <tok>	 DEFAULTS_CMND		/* Command-specific defaults entry */
144%token <tok> 	 NOPASSWD		/* no passwd req for command */
145%token <tok> 	 PASSWD			/* passwd req for command (default) */
146%token <tok> 	 NOEXEC			/* preload dummy execve() for cmnd */
147%token <tok> 	 EXEC			/* don't preload dummy execve() */
148%token <tok>	 SETENV			/* user may set environment for cmnd */
149%token <tok>	 NOSETENV		/* user may not set environment */
150%token <tok>	 LOG_INPUT		/* log user's cmnd input */
151%token <tok>	 NOLOG_INPUT		/* don't log user's cmnd input */
152%token <tok>	 LOG_OUTPUT		/* log cmnd output */
153%token <tok>	 NOLOG_OUTPUT		/* don't log cmnd output */
154%token <tok>	 ALL			/* ALL keyword */
155%token <tok>	 COMMENT		/* comment and/or carriage return */
156%token <tok>	 HOSTALIAS		/* Host_Alias keyword */
157%token <tok>	 CMNDALIAS		/* Cmnd_Alias keyword */
158%token <tok>	 USERALIAS		/* User_Alias keyword */
159%token <tok>	 RUNASALIAS		/* Runas_Alias keyword */
160%token <tok>	 ':' '=' ',' '!' '+' '-' /* union member tokens */
161%token <tok>	 '(' ')'		/* runas tokens */
162%token <tok>	 ERROR
163%token <tok>	 TYPE			/* SELinux type */
164%token <tok>	 ROLE			/* SELinux role */
165
166%type <cmndspec>  cmndspec
167%type <cmndspec>  cmndspeclist
168%type <defaults>  defaults_entry
169%type <defaults>  defaults_list
170%type <member>	  cmnd
171%type <member>	  opcmnd
172%type <member>	  cmndlist
173%type <member>	  host
174%type <member>	  hostlist
175%type <member>	  ophost
176%type <member>	  opuser
177%type <member>	  user
178%type <member>	  userlist
179%type <member>	  opgroup
180%type <member>	  group
181%type <member>	  grouplist
182%type <runas>	  runasspec
183%type <runas>	  runaslist
184%type <privilege> privilege
185%type <privilege> privileges
186%type <tag>	  cmndtag
187%type <seinfo>	  selinux
188%type <string>	  rolespec
189%type <string>	  typespec
190
191%%
192
193file		:	{ ; }
194		|	line
195		;
196
197line		:	entry
198		|	line entry
199		;
200
201entry		:	COMMENT {
202			    ;
203			}
204                |       error COMMENT {
205			    yyerrok;
206			}
207		|	userlist privileges {
208			    add_userspec($1, $2);
209			}
210		|	USERALIAS useraliases {
211			    ;
212			}
213		|	HOSTALIAS hostaliases {
214			    ;
215			}
216		|	CMNDALIAS cmndaliases {
217			    ;
218			}
219		|	RUNASALIAS runasaliases {
220			    ;
221			}
222		|	DEFAULTS defaults_list {
223			    add_defaults(DEFAULTS, NULL, $2);
224			}
225		|	DEFAULTS_USER userlist defaults_list {
226			    add_defaults(DEFAULTS_USER, $2, $3);
227			}
228		|	DEFAULTS_RUNAS userlist defaults_list {
229			    add_defaults(DEFAULTS_RUNAS, $2, $3);
230			}
231		|	DEFAULTS_HOST hostlist defaults_list {
232			    add_defaults(DEFAULTS_HOST, $2, $3);
233			}
234		|	DEFAULTS_CMND cmndlist defaults_list {
235			    add_defaults(DEFAULTS_CMND, $2, $3);
236			}
237		;
238
239defaults_list	:	defaults_entry
240		|	defaults_list ',' defaults_entry {
241			    list_append($1, $3);
242			    $$ = $1;
243			}
244		;
245
246defaults_entry	:	DEFVAR {
247			    $$ = new_default($1, NULL, TRUE);
248			}
249		|	'!' DEFVAR {
250			    $$ = new_default($2, NULL, FALSE);
251			}
252		|	DEFVAR '=' WORD {
253			    $$ = new_default($1, $3, TRUE);
254			}
255		|	DEFVAR '+' WORD {
256			    $$ = new_default($1, $3, '+');
257			}
258		|	DEFVAR '-' WORD {
259			    $$ = new_default($1, $3, '-');
260			}
261		;
262
263privileges	:	privilege
264		|	privileges ':' privilege {
265			    list_append($1, $3);
266			    $$ = $1;
267			}
268		;
269
270privilege	:	hostlist '=' cmndspeclist {
271			    struct privilege *p = emalloc(sizeof(*p));
272			    list2tq(&p->hostlist, $1);
273			    list2tq(&p->cmndlist, $3);
274			    p->prev = p;
275			    p->next = NULL;
276			    $$ = p;
277			}
278		;
279
280ophost		:	host {
281			    $$ = $1;
282			    $$->negated = FALSE;
283			}
284		|	'!' host {
285			    $$ = $2;
286			    $$->negated = TRUE;
287			}
288		;
289
290host		:	ALIAS {
291			    $$ = new_member($1, ALIAS);
292			}
293		|	ALL {
294			    $$ = new_member(NULL, ALL);
295			}
296		|	NETGROUP {
297			    $$ = new_member($1, NETGROUP);
298			}
299		|	NTWKADDR {
300			    $$ = new_member($1, NTWKADDR);
301			}
302		|	WORD {
303			    $$ = new_member($1, WORD);
304			}
305		;
306
307cmndspeclist	:	cmndspec
308		|	cmndspeclist ',' cmndspec {
309			    list_append($1, $3);
310#ifdef HAVE_SELINUX
311			    /* propagate role and type */
312			    if ($3->role == NULL)
313				$3->role = $3->prev->role;
314			    if ($3->type == NULL)
315				$3->type = $3->prev->type;
316#endif /* HAVE_SELINUX */
317			    /* propagate tags and runas list */
318			    if ($3->tags.nopasswd == UNSPEC)
319				$3->tags.nopasswd = $3->prev->tags.nopasswd;
320			    if ($3->tags.noexec == UNSPEC)
321				$3->tags.noexec = $3->prev->tags.noexec;
322			    if ($3->tags.setenv == UNSPEC &&
323				$3->prev->tags.setenv != IMPLIED)
324				$3->tags.setenv = $3->prev->tags.setenv;
325			    if ($3->tags.log_input == UNSPEC)
326				$3->tags.log_input = $3->prev->tags.log_input;
327			    if ($3->tags.log_output == UNSPEC)
328				$3->tags.log_output = $3->prev->tags.log_output;
329			    if ((tq_empty(&$3->runasuserlist) &&
330				 tq_empty(&$3->runasgrouplist)) &&
331				(!tq_empty(&$3->prev->runasuserlist) ||
332				 !tq_empty(&$3->prev->runasgrouplist))) {
333				$3->runasuserlist = $3->prev->runasuserlist;
334				$3->runasgrouplist = $3->prev->runasgrouplist;
335			    }
336			    $$ = $1;
337			}
338		;
339
340cmndspec	:	runasspec selinux cmndtag opcmnd {
341			    struct cmndspec *cs = emalloc(sizeof(*cs));
342			    if ($1 != NULL) {
343				list2tq(&cs->runasuserlist, $1->runasusers);
344				list2tq(&cs->runasgrouplist, $1->runasgroups);
345				efree($1);
346			    } else {
347				tq_init(&cs->runasuserlist);
348				tq_init(&cs->runasgrouplist);
349			    }
350#ifdef HAVE_SELINUX
351			    cs->role = $2.role;
352			    cs->type = $2.type;
353#endif
354			    cs->tags = $3;
355			    cs->cmnd = $4;
356			    cs->prev = cs;
357			    cs->next = NULL;
358			    /* sudo "ALL" implies the SETENV tag */
359			    if (cs->cmnd->type == ALL && !cs->cmnd->negated &&
360				cs->tags.setenv == UNSPEC)
361				cs->tags.setenv = IMPLIED;
362			    $$ = cs;
363			}
364		;
365
366opcmnd		:	cmnd {
367			    $$ = $1;
368			    $$->negated = FALSE;
369			}
370		|	'!' cmnd {
371			    $$ = $2;
372			    $$->negated = TRUE;
373			}
374		;
375
376rolespec	:	ROLE '=' WORD {
377			    $$ = $3;
378			}
379		;
380
381typespec	:	TYPE '=' WORD {
382			    $$ = $3;
383			}
384		;
385
386selinux		:	/* empty */ {
387			    $$.role = NULL;
388			    $$.type = NULL;
389			}
390		|	rolespec {
391			    $$.role = $1;
392			    $$.type = NULL;
393			}
394		|	typespec {
395			    $$.type = $1;
396			    $$.role = NULL;
397			}
398		|	rolespec typespec {
399			    $$.role = $1;
400			    $$.type = $2;
401			}
402		|	typespec rolespec {
403			    $$.type = $1;
404			    $$.role = $2;
405			}
406		;
407
408runasspec	:	/* empty */ {
409			    $$ = NULL;
410			}
411		|	'(' runaslist ')' {
412			    $$ = $2;
413			}
414		;
415
416runaslist	:	userlist {
417			    $$ = emalloc(sizeof(struct runascontainer));
418			    $$->runasusers = $1;
419			    $$->runasgroups = NULL;
420			}
421		|	userlist ':' grouplist {
422			    $$ = emalloc(sizeof(struct runascontainer));
423			    $$->runasusers = $1;
424			    $$->runasgroups = $3;
425			}
426		|	':' grouplist {
427			    $$ = emalloc(sizeof(struct runascontainer));
428			    $$->runasusers = NULL;
429			    $$->runasgroups = $2;
430			}
431		;
432
433cmndtag		:	/* empty */ {
434			    $$.nopasswd = $$.noexec = $$.setenv =
435				$$.log_input = $$.log_output = UNSPEC;
436			}
437		|	cmndtag NOPASSWD {
438			    $$.nopasswd = TRUE;
439			}
440		|	cmndtag PASSWD {
441			    $$.nopasswd = FALSE;
442			}
443		|	cmndtag NOEXEC {
444			    $$.noexec = TRUE;
445			}
446		|	cmndtag EXEC {
447			    $$.noexec = FALSE;
448			}
449		|	cmndtag SETENV {
450			    $$.setenv = TRUE;
451			}
452		|	cmndtag NOSETENV {
453			    $$.setenv = FALSE;
454			}
455		|	cmndtag LOG_INPUT {
456			    $$.log_input = TRUE;
457			}
458		|	cmndtag NOLOG_INPUT {
459			    $$.log_input = FALSE;
460			}
461		|	cmndtag LOG_OUTPUT {
462			    $$.log_output = TRUE;
463			}
464		|	cmndtag NOLOG_OUTPUT {
465			    $$.log_output = FALSE;
466			}
467		;
468
469cmnd		:	ALL {
470			    $$ = new_member(NULL, ALL);
471			}
472		|	ALIAS {
473			    $$ = new_member($1, ALIAS);
474			}
475		|	COMMAND {
476			    struct sudo_command *c = emalloc(sizeof(*c));
477			    c->cmnd = $1.cmnd;
478			    c->args = $1.args;
479			    $$ = new_member((char *)c, COMMAND);
480			}
481		;
482
483hostaliases	:	hostalias
484		|	hostaliases ':' hostalias
485		;
486
487hostalias	:	ALIAS '=' hostlist {
488			    char *s;
489			    if ((s = alias_add($1, HOSTALIAS, $3)) != NULL) {
490				yyerror(s);
491				YYERROR;
492			    }
493			}
494		;
495
496hostlist	:	ophost
497		|	hostlist ',' ophost {
498			    list_append($1, $3);
499			    $$ = $1;
500			}
501		;
502
503cmndaliases	:	cmndalias
504		|	cmndaliases ':' cmndalias
505		;
506
507cmndalias	:	ALIAS '=' cmndlist {
508			    char *s;
509			    if ((s = alias_add($1, CMNDALIAS, $3)) != NULL) {
510				yyerror(s);
511				YYERROR;
512			    }
513			}
514		;
515
516cmndlist	:	opcmnd
517		|	cmndlist ',' opcmnd {
518			    list_append($1, $3);
519			    $$ = $1;
520			}
521		;
522
523runasaliases	:	runasalias
524		|	runasaliases ':' runasalias
525		;
526
527runasalias	:	ALIAS '=' userlist {
528			    char *s;
529			    if ((s = alias_add($1, RUNASALIAS, $3)) != NULL) {
530				yyerror(s);
531				YYERROR;
532			    }
533			}
534		;
535
536useraliases	:	useralias
537		|	useraliases ':' useralias
538		;
539
540useralias	:	ALIAS '=' userlist {
541			    char *s;
542			    if ((s = alias_add($1, USERALIAS, $3)) != NULL) {
543				yyerror(s);
544				YYERROR;
545			    }
546			}
547		;
548
549userlist	:	opuser
550		|	userlist ',' opuser {
551			    list_append($1, $3);
552			    $$ = $1;
553			}
554		;
555
556opuser		:	user {
557			    $$ = $1;
558			    $$->negated = FALSE;
559			}
560		|	'!' user {
561			    $$ = $2;
562			    $$->negated = TRUE;
563			}
564		;
565
566user		:	ALIAS {
567			    $$ = new_member($1, ALIAS);
568			}
569		|	ALL {
570			    $$ = new_member(NULL, ALL);
571			}
572		|	NETGROUP {
573			    $$ = new_member($1, NETGROUP);
574			}
575		|	USERGROUP {
576			    $$ = new_member($1, USERGROUP);
577			}
578		|	WORD {
579			    $$ = new_member($1, WORD);
580			}
581		;
582
583grouplist	:	opgroup
584		|	grouplist ',' opgroup {
585			    list_append($1, $3);
586			    $$ = $1;
587			}
588		;
589
590opgroup		:	group {
591			    $$ = $1;
592			    $$->negated = FALSE;
593			}
594		|	'!' group {
595			    $$ = $2;
596			    $$->negated = TRUE;
597			}
598		;
599
600group		:	ALIAS {
601			    $$ = new_member($1, ALIAS);
602			}
603		|	ALL {
604			    $$ = new_member(NULL, ALL);
605			}
606		|	WORD {
607			    $$ = new_member($1, WORD);
608			}
609		;
610
611%%
612static struct defaults *
613new_default(var, val, op)
614    char *var;
615    char *val;
616    int op;
617{
618    struct defaults *d;
619
620    d = emalloc(sizeof(struct defaults));
621    d->var = var;
622    d->val = val;
623    tq_init(&d->binding);
624    d->type = 0;
625    d->op = op;
626    d->prev = d;
627    d->next = NULL;
628
629    return d;
630}
631
632static struct member *
633new_member(name, type)
634    char *name;
635    int type;
636{
637    struct member *m;
638
639    m = emalloc(sizeof(struct member));
640    m->name = name;
641    m->type = type;
642    m->prev = m;
643    m->next = NULL;
644
645    return m;
646}
647
648/*
649 * Add a list of defaults structures to the defaults list.
650 * The binding, if non-NULL, specifies a list of hosts, users, or
651 * runas users the entries apply to (specified by the type).
652 */
653static void
654add_defaults(type, bmem, defs)
655    int type;
656    struct member *bmem;
657    struct defaults *defs;
658{
659    struct defaults *d;
660    struct member_list binding;
661
662    /*
663     * We can only call list2tq once on bmem as it will zero
664     * out the prev pointer when it consumes bmem.
665     */
666    list2tq(&binding, bmem);
667
668    /*
669     * Set type and binding (who it applies to) for new entries.
670     */
671    for (d = defs; d != NULL; d = d->next) {
672	d->type = type;
673	d->binding = binding;
674    }
675    tq_append(&defaults, defs);
676}
677
678/*
679 * Allocate a new struct userspec, populate it, and insert it at the
680 * and of the userspecs list.
681 */
682static void
683add_userspec(members, privs)
684    struct member *members;
685    struct privilege *privs;
686{
687    struct userspec *u;
688
689    u = emalloc(sizeof(*u));
690    list2tq(&u->users, members);
691    list2tq(&u->privileges, privs);
692    u->prev = u;
693    u->next = NULL;
694    tq_append(&userspecs, u);
695}
696
697/*
698 * Free up space used by data structures from a previous parser run and sets
699 * the current sudoers file to path.
700 */
701void
702init_parser(path, quiet)
703    char *path;
704    int quiet;
705{
706    struct defaults *d;
707    struct member *m, *binding;
708    struct userspec *us;
709    struct privilege *priv;
710    struct cmndspec *cs;
711    struct sudo_command *c;
712
713    while ((us = tq_pop(&userspecs)) != NULL) {
714	while ((m = tq_pop(&us->users)) != NULL) {
715	    efree(m->name);
716	    efree(m);
717	}
718	while ((priv = tq_pop(&us->privileges)) != NULL) {
719	    struct member *runasuser = NULL, *runasgroup = NULL;
720#ifdef HAVE_SELINUX
721	    char *role = NULL, *type = NULL;
722#endif /* HAVE_SELINUX */
723
724	    while ((m = tq_pop(&priv->hostlist)) != NULL) {
725		efree(m->name);
726		efree(m);
727	    }
728	    while ((cs = tq_pop(&priv->cmndlist)) != NULL) {
729#ifdef HAVE_SELINUX
730		/* Only free the first instance of a role/type. */
731		if (cs->role != role) {
732		    role = cs->role;
733		    efree(cs->role);
734		}
735		if (cs->type != type) {
736		    type = cs->type;
737		    efree(cs->type);
738		}
739#endif /* HAVE_SELINUX */
740		if (tq_last(&cs->runasuserlist) != runasuser) {
741		    runasuser = tq_last(&cs->runasuserlist);
742		    while ((m = tq_pop(&cs->runasuserlist)) != NULL) {
743			efree(m->name);
744			efree(m);
745		    }
746		}
747		if (tq_last(&cs->runasgrouplist) != runasgroup) {
748		    runasgroup = tq_last(&cs->runasgrouplist);
749		    while ((m = tq_pop(&cs->runasgrouplist)) != NULL) {
750			efree(m->name);
751			efree(m);
752		    }
753		}
754		if (cs->cmnd->type == COMMAND) {
755			c = (struct sudo_command *) cs->cmnd->name;
756			efree(c->cmnd);
757			efree(c->args);
758		}
759		efree(cs->cmnd->name);
760		efree(cs->cmnd);
761		efree(cs);
762	    }
763	    efree(priv);
764	}
765	efree(us);
766    }
767    tq_init(&userspecs);
768
769    binding = NULL;
770    while ((d = tq_pop(&defaults)) != NULL) {
771	if (tq_last(&d->binding) != binding) {
772	    binding = tq_last(&d->binding);
773	    while ((m = tq_pop(&d->binding)) != NULL) {
774		if (m->type == COMMAND) {
775			c = (struct sudo_command *) m->name;
776			efree(c->cmnd);
777			efree(c->args);
778		}
779		efree(m->name);
780		efree(m);
781	    }
782	}
783	efree(d->var);
784	efree(d->val);
785	efree(d);
786    }
787    tq_init(&defaults);
788
789    init_aliases();
790
791    init_lexer();
792
793    efree(sudoers);
794    sudoers = path ? estrdup(path) : NULL;
795
796    parse_error = FALSE;
797    errorlineno = -1;
798    errorfile = sudoers;
799    sudoers_warnings = !quiet;
800}
801