1: %{
2: /*
3: * Copyright (c) 1996, 1998-2005, 2007-2013
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: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
18: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
19: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
20: *
21: * Sponsored in part by the Defense Advanced Research Projects
22: * Agency (DARPA) and Air Force Research Laboratory, Air Force
23: * Materiel Command, USAF, under agreement number F39502-99-1-0512.
24: */
25:
26: #include <config.h>
27:
28: #include <sys/types.h>
29: #include <sys/stat.h>
30: #include <stdio.h>
31: #ifdef STDC_HEADERS
32: # include <stdlib.h>
33: # include <stddef.h>
34: #else
35: # ifdef HAVE_STDLIB_H
36: # include <stdlib.h>
37: # endif
38: #endif /* STDC_HEADERS */
39: #ifdef HAVE_STRING_H
40: # include <string.h>
41: #endif /* HAVE_STRING_H */
42: #ifdef HAVE_STRINGS_H
43: # include <strings.h>
44: #endif /* HAVE_STRINGS_H */
45: #if defined(HAVE_STDINT_H)
46: # include <stdint.h>
47: #elif defined(HAVE_INTTYPES_H)
48: # include <inttypes.h>
49: #endif
50: #ifdef HAVE_UNISTD_H
51: # include <unistd.h>
52: #endif /* HAVE_UNISTD_H */
53: #if defined(HAVE_MALLOC_H) && !defined(STDC_HEADERS)
54: # include <malloc.h>
55: #endif /* HAVE_MALLOC_H && !STDC_HEADERS */
56: #ifdef HAVE_DIRENT_H
57: # include <dirent.h>
58: # define NAMLEN(dirent) strlen((dirent)->d_name)
59: #else
60: # define dirent direct
61: # define NAMLEN(dirent) (dirent)->d_namlen
62: # ifdef HAVE_SYS_NDIR_H
63: # include <sys/ndir.h>
64: # endif
65: # ifdef HAVE_SYS_DIR_H
66: # include <sys/dir.h>
67: # endif
68: # ifdef HAVE_NDIR_H
69: # include <ndir.h>
70: # endif
71: #endif
72: #include <errno.h>
73: #include <ctype.h>
74: #include "sudoers.h"
75: #include "parse.h"
76: #include "toke.h"
77: #include <gram.h>
78: #include "lbuf.h"
79: #include "sha2.h"
80: #include "secure_path.h"
81:
82: int sudolineno; /* current sudoers line number. */
83: int last_token; /* last token that was parsed. */
84: char *sudoers; /* sudoers file being parsed. */
85:
86: /* Default sudoers path, mode and owner (may be set via sudo.conf) */
87: const char *sudoers_file = _PATH_SUDOERS;
88: mode_t sudoers_mode = SUDOERS_MODE;
89: uid_t sudoers_uid = SUDOERS_UID;
90: gid_t sudoers_gid = SUDOERS_GID;
91:
92: static bool continued, sawspace;
93: static int prev_state;
94: static int digest_len;
95:
96: static bool _push_include(char *, bool);
97: static bool pop_include(void);
98: static char *parse_include(char *);
99:
100: int (*trace_print)(const char *msg) = sudoers_trace_print;
101:
102: #define LEXRETURN(n) do { \
103: last_token = (n); \
104: return (n); \
105: } while (0)
106:
107: #define ECHO ignore_result(fwrite(sudoerstext, sudoersleng, 1, sudoersout))
108:
109: #define push_include(_p) (_push_include((_p), false))
110: #define push_includedir(_p) (_push_include((_p), true))
111: %}
112:
113: HEX16 [0-9A-Fa-f]{1,4}
114: OCTET (1?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5])
115: IPV4ADDR {OCTET}(\.{OCTET}){3}
116: IPV6ADDR ({HEX16}?:){2,7}{HEX16}?|({HEX16}?:){2,6}:{IPV4ADDR}
117:
118: HOSTNAME [[:alnum:]_-]+
119: WORD ([^#>!=:,\(\) \t\n\\\"]|\\[^\n])+
120: ID #-?[0-9]+
121: PATH \/(\\[\,:= \t#]|[^\,:=\\ \t\n#])+
122: ENVAR ([^#!=, \t\n\\\"]|\\[^\n])([^#=, \t\n\\\"]|\\[^\n])*
123: DEFVAR [a-z_]+
124:
125: %option noinput
126: %option nounput
127: %option noyywrap
128: %option prefix="sudoers"
129:
130: %s GOTDEFS
131: %x GOTCMND
132: %x STARTDEFS
133: %x INDEFS
134: %x INSTR
135: %s WANTDIGEST
136:
137: %%
138: <GOTDEFS>[[:blank:]]*,[[:blank:]]* {
139: LEXTRACE(", ");
140: LEXRETURN(',');
141: } /* return ',' */
142:
143: <GOTDEFS>[[:blank:]]+ BEGIN STARTDEFS;
144:
145: <STARTDEFS>{DEFVAR} {
146: BEGIN INDEFS;
147: LEXTRACE("DEFVAR ");
148: if (!fill(sudoerstext, sudoersleng))
149: yyterminate();
150: LEXRETURN(DEFVAR);
151: }
152:
153: <INDEFS>{
154: , {
155: BEGIN STARTDEFS;
156: LEXTRACE(", ");
157: LEXRETURN(',');
158: } /* return ',' */
159:
160: = {
161: LEXTRACE("= ");
162: LEXRETURN('=');
163: } /* return '=' */
164:
165: \+= {
166: LEXTRACE("+= ");
167: LEXRETURN('+');
168: } /* return '+' */
169:
170: -= {
171: LEXTRACE("-= ");
172: LEXRETURN('-');
173: } /* return '-' */
174:
175: \" {
176: LEXTRACE("BEGINSTR ");
177: sudoerslval.string = NULL;
178: prev_state = YY_START;
179: BEGIN INSTR;
180: }
181:
182: {ENVAR} {
183: LEXTRACE("WORD(2) ");
184: if (!fill(sudoerstext, sudoersleng))
185: yyterminate();
186: LEXRETURN(WORD);
187: }
188: }
189:
190: <INSTR>{
191: \\[[:blank:]]*\n[[:blank:]]* {
192: /* Line continuation char followed by newline. */
193: sudolineno++;
194: continued = true;
195: }
196:
197: \" {
198: LEXTRACE("ENDSTR ");
199: BEGIN prev_state;
200:
201: if (sudoerslval.string == NULL) {
202: LEXTRACE("ERROR "); /* empty string */
203: LEXRETURN(ERROR);
204: }
205: if (prev_state == INITIAL) {
206: switch (sudoerslval.string[0]) {
207: case '%':
208: if (sudoerslval.string[1] == '\0' ||
209: (sudoerslval.string[1] == ':' &&
210: sudoerslval.string[2] == '\0')) {
211: LEXTRACE("ERROR "); /* empty group */
212: LEXRETURN(ERROR);
213: }
214: LEXTRACE("USERGROUP ");
215: LEXRETURN(USERGROUP);
216: case '+':
217: if (sudoerslval.string[1] == '\0') {
218: LEXTRACE("ERROR "); /* empty netgroup */
219: LEXRETURN(ERROR);
220: }
221: LEXTRACE("NETGROUP ");
222: LEXRETURN(NETGROUP);
223: }
224: }
225: LEXTRACE("WORD(4) ");
226: LEXRETURN(WORD);
227: }
228:
229: \\ {
230: LEXTRACE("BACKSLASH ");
231: if (!append(sudoerstext, sudoersleng))
232: yyterminate();
233: }
234:
235: ([^\"\n\\]|\\\")+ {
236: LEXTRACE("STRBODY ");
237: if (!append(sudoerstext, sudoersleng))
238: yyterminate();
239: }
240: }
241:
242: <GOTCMND>{
243: \\[\*\?\[\]\!] {
244: /* quoted fnmatch glob char, pass verbatim */
245: LEXTRACE("QUOTEDCHAR ");
246: if (!fill_args(sudoerstext, 2, sawspace))
247: yyterminate();
248: sawspace = false;
249: }
250:
251: \\[:\\,= \t#] {
252: /* quoted sudoers special char, strip backslash */
253: LEXTRACE("QUOTEDCHAR ");
254: if (!fill_args(sudoerstext + 1, 1, sawspace))
255: yyterminate();
256: sawspace = false;
257: }
258:
259: [#:\,=\n] {
260: BEGIN INITIAL;
261: yyless(0);
262: LEXRETURN(COMMAND);
263: } /* end of command line args */
264:
265: [^#\\:, \t\n]+ {
266: LEXTRACE("ARG ");
267: if (!fill_args(sudoerstext, sudoersleng, sawspace))
268: yyterminate();
269: sawspace = false;
270: } /* a command line arg */
271: }
272:
273: <WANTDIGEST>[[:xdigit:]]+ {
274: /* Only return DIGEST if the length is correct. */
275: if (sudoersleng == digest_len * 2) {
276: if (!fill(sudoerstext, sudoersleng))
277: yyterminate();
278: BEGIN INITIAL;
279: LEXTRACE("DIGEST ");
280: LEXRETURN(DIGEST);
281: }
282: BEGIN INITIAL;
283: yyless(sudoersleng);
284: } /* hex digest */
285:
286: <WANTDIGEST>[A-Za-z0-9\+/=]+ {
287: /* Only return DIGEST if the length is correct. */
288: int len;
289: if (sudoerstext[sudoersleng - 1] == '=') {
290: /* use padding */
291: len = 4 * ((digest_len + 2) / 3);
292: } else {
293: /* no padding */
294: len = (4 * digest_len + 2) / 3;
295: }
296: if (sudoersleng == len) {
297: if (!fill(sudoerstext, sudoersleng))
298: yyterminate();
299: BEGIN INITIAL;
300: LEXTRACE("DIGEST ");
301: LEXRETURN(DIGEST);
302: }
303: BEGIN INITIAL;
304: yyless(sudoersleng);
305: } /* base64 digest */
306:
307: <INITIAL>^#include[[:blank:]]+.*\n {
308: char *path;
309:
310: if (continued) {
311: LEXTRACE("ERROR ");
312: LEXRETURN(ERROR);
313: }
314:
315: if ((path = parse_include(sudoerstext)) == NULL)
316: yyterminate();
317:
318: LEXTRACE("INCLUDE\n");
319:
320: /* Push current buffer and switch to include file */
321: if (!push_include(path))
322: yyterminate();
323: }
324:
325: <INITIAL>^#includedir[[:blank:]]+.*\n {
326: char *path;
327:
328: if (continued) {
329: LEXTRACE("ERROR ");
330: LEXRETURN(ERROR);
331: }
332:
333: if ((path = parse_include(sudoerstext)) == NULL)
334: yyterminate();
335:
336: LEXTRACE("INCLUDEDIR\n");
337:
338: /*
339: * Push current buffer and switch to include file.
340: * We simply ignore empty directories.
341: */
342: if (!push_includedir(path) && parse_error)
343: yyterminate();
344: }
345:
346: <INITIAL>^[[:blank:]]*Defaults([:@>\!][[:blank:]]*\!*\"?({ID}|{WORD}))? {
347: char deftype;
348: int n;
349:
350: if (continued) {
351: LEXTRACE("ERROR ");
352: LEXRETURN(ERROR);
353: }
354:
355: for (n = 0; isblank((unsigned char)sudoerstext[n]); n++)
356: continue;
357: n += sizeof("Defaults") - 1;
358: if ((deftype = sudoerstext[n++]) != '\0') {
359: while (isblank((unsigned char)sudoerstext[n]))
360: n++;
361: }
362: BEGIN GOTDEFS;
363: switch (deftype) {
364: case ':':
365: yyless(n);
366: LEXTRACE("DEFAULTS_USER ");
367: LEXRETURN(DEFAULTS_USER);
368: case '>':
369: yyless(n);
370: LEXTRACE("DEFAULTS_RUNAS ");
371: LEXRETURN(DEFAULTS_RUNAS);
372: case '@':
373: yyless(n);
374: LEXTRACE("DEFAULTS_HOST ");
375: LEXRETURN(DEFAULTS_HOST);
376: case '!':
377: yyless(n);
378: LEXTRACE("DEFAULTS_CMND ");
379: LEXRETURN(DEFAULTS_CMND);
380: default:
381: LEXTRACE("DEFAULTS ");
382: LEXRETURN(DEFAULTS);
383: }
384: }
385:
386: <INITIAL>^[[:blank:]]*(Host|Cmnd|User|Runas)_Alias {
387: int n;
388:
389: if (continued) {
390: LEXTRACE("ERROR ");
391: LEXRETURN(ERROR);
392: }
393:
394: for (n = 0; isblank((unsigned char)sudoerstext[n]); n++)
395: continue;
396: switch (sudoerstext[n]) {
397: case 'H':
398: LEXTRACE("HOSTALIAS ");
399: LEXRETURN(HOSTALIAS);
400: case 'C':
401: LEXTRACE("CMNDALIAS ");
402: LEXRETURN(CMNDALIAS);
403: case 'U':
404: LEXTRACE("USERALIAS ");
405: LEXRETURN(USERALIAS);
406: case 'R':
407: LEXTRACE("RUNASALIAS ");
408: LEXRETURN(RUNASALIAS);
409: }
410: }
411:
412: NOPASSWD[[:blank:]]*: {
413: /* cmnd does not require passwd for this user */
414: LEXTRACE("NOPASSWD ");
415: LEXRETURN(NOPASSWD);
416: }
417:
418: PASSWD[[:blank:]]*: {
419: /* cmnd requires passwd for this user */
420: LEXTRACE("PASSWD ");
421: LEXRETURN(PASSWD);
422: }
423:
424: NOEXEC[[:blank:]]*: {
425: LEXTRACE("NOEXEC ");
426: LEXRETURN(NOEXEC);
427: }
428:
429: EXEC[[:blank:]]*: {
430: LEXTRACE("EXEC ");
431: LEXRETURN(EXEC);
432: }
433:
434: SETENV[[:blank:]]*: {
435: LEXTRACE("SETENV ");
436: LEXRETURN(SETENV);
437: }
438:
439: NOSETENV[[:blank:]]*: {
440: LEXTRACE("NOSETENV ");
441: LEXRETURN(NOSETENV);
442: }
443:
444: LOG_OUTPUT[[:blank:]]*: {
445: LEXTRACE("LOG_OUTPUT ");
446: LEXRETURN(LOG_OUTPUT);
447: }
448:
449: NOLOG_OUTPUT[[:blank:]]*: {
450: LEXTRACE("NOLOG_OUTPUT ");
451: LEXRETURN(NOLOG_OUTPUT);
452: }
453:
454: LOG_INPUT[[:blank:]]*: {
455: LEXTRACE("LOG_INPUT ");
456: LEXRETURN(LOG_INPUT);
457: }
458:
459: NOLOG_INPUT[[:blank:]]*: {
460: LEXTRACE("NOLOG_INPUT ");
461: LEXRETURN(NOLOG_INPUT);
462: }
463:
464: <INITIAL,GOTDEFS>(\+|\%|\%:) {
465: /* empty group or netgroup */
466: LEXTRACE("ERROR ");
467: LEXRETURN(ERROR);
468: }
469:
470: \+{WORD} {
471: /* netgroup */
472: if (!fill(sudoerstext, sudoersleng))
473: yyterminate();
474: LEXTRACE("NETGROUP ");
475: LEXRETURN(NETGROUP);
476: }
477:
478: \%:?({WORD}|{ID}) {
479: /* group */
480: if (!fill(sudoerstext, sudoersleng))
481: yyterminate();
482: LEXTRACE("USERGROUP ");
483: LEXRETURN(USERGROUP);
484: }
485:
486: {IPV4ADDR}(\/{IPV4ADDR})? {
487: if (!fill(sudoerstext, sudoersleng))
488: yyterminate();
489: LEXTRACE("NTWKADDR ");
490: LEXRETURN(NTWKADDR);
491: }
492:
493: {IPV4ADDR}\/([12]?[0-9]|3[0-2]) {
494: if (!fill(sudoerstext, sudoersleng))
495: yyterminate();
496: LEXTRACE("NTWKADDR ");
497: LEXRETURN(NTWKADDR);
498: }
499:
500: {IPV6ADDR}(\/{IPV6ADDR})? {
501: if (!ipv6_valid(sudoerstext)) {
502: LEXTRACE("ERROR ");
503: LEXRETURN(ERROR);
504: }
505: if (!fill(sudoerstext, sudoersleng))
506: yyterminate();
507: LEXTRACE("NTWKADDR ");
508: LEXRETURN(NTWKADDR);
509: }
510:
511: {IPV6ADDR}\/([0-9]|[1-9][0-9]|1[01][0-9]|12[0-8]) {
512: if (!ipv6_valid(sudoerstext)) {
513: LEXTRACE("ERROR ");
514: LEXRETURN(ERROR);
515: }
516: if (!fill(sudoerstext, sudoersleng))
517: yyterminate();
518: LEXTRACE("NTWKADDR ");
519: LEXRETURN(NTWKADDR);
520: }
521:
522: ALL {
523: LEXTRACE("ALL ");
524: LEXRETURN(ALL);
525:
526: }
527:
528: <INITIAL>ROLE {
529: #ifdef HAVE_SELINUX
530: LEXTRACE("ROLE ");
531: LEXRETURN(ROLE);
532: #else
533: goto got_alias;
534: #endif
535: }
536:
537: <INITIAL>TYPE {
538: #ifdef HAVE_SELINUX
539: LEXTRACE("TYPE ");
540: LEXRETURN(TYPE);
541: #else
542: goto got_alias;
543: #endif
544: }
545: <INITIAL>PRIVS {
546: #ifdef HAVE_PRIV_SET
547: LEXTRACE("PRIVS ");
548: LEXRETURN(PRIVS);
549: #else
550: goto got_alias;
551: #endif
552: }
553:
554: <INITIAL>LIMITPRIVS {
555: #ifdef HAVE_PRIV_SET
556: LEXTRACE("LIMITPRIVS ");
557: LEXRETURN(LIMITPRIVS);
558: #else
559: goto got_alias;
560: #endif
561: }
562:
563: [[:upper:]][[:upper:][:digit:]_]* {
564: got_alias:
565: if (!fill(sudoerstext, sudoersleng))
566: yyterminate();
567: LEXTRACE("ALIAS ");
568: LEXRETURN(ALIAS);
569: }
570:
571: <GOTDEFS>({PATH}|sudoedit) {
572: /* XXX - no way to specify digest for command */
573: /* no command args allowed for Defaults!/path */
574: if (!fill_cmnd(sudoerstext, sudoersleng))
575: yyterminate();
576: LEXTRACE("COMMAND ");
577: LEXRETURN(COMMAND);
578: }
579:
580: sha224 {
581: digest_len = SHA224_DIGEST_LENGTH;
582: BEGIN WANTDIGEST;
583: LEXTRACE("SHA224 ");
584: LEXRETURN(SHA224);
585: }
586:
587: sha256 {
588: digest_len = SHA256_DIGEST_LENGTH;
589: BEGIN WANTDIGEST;
590: LEXTRACE("SHA256 ");
591: LEXRETURN(SHA256);
592: }
593:
594: sha384 {
595: digest_len = SHA384_DIGEST_LENGTH;
596: BEGIN WANTDIGEST;
597: LEXTRACE("SHA384 ");
598: LEXRETURN(SHA384);
599: }
600:
601: sha512 {
602: digest_len = SHA512_DIGEST_LENGTH;
603: BEGIN WANTDIGEST;
604: LEXTRACE("SHA512 ");
605: LEXRETURN(SHA512);
606: }
607:
608: sudoedit {
609: BEGIN GOTCMND;
610: LEXTRACE("COMMAND ");
611: if (!fill_cmnd(sudoerstext, sudoersleng))
612: yyterminate();
613: } /* sudo -e */
614:
615: {PATH} {
616: /* directories can't have args... */
617: if (sudoerstext[sudoersleng - 1] == '/') {
618: LEXTRACE("COMMAND ");
619: if (!fill_cmnd(sudoerstext, sudoersleng))
620: yyterminate();
621: LEXRETURN(COMMAND);
622: } else {
623: BEGIN GOTCMND;
624: LEXTRACE("COMMAND ");
625: if (!fill_cmnd(sudoerstext, sudoersleng))
626: yyterminate();
627: }
628: } /* a pathname */
629:
630: <INITIAL,GOTDEFS>\" {
631: LEXTRACE("BEGINSTR ");
632: sudoerslval.string = NULL;
633: prev_state = YY_START;
634: BEGIN INSTR;
635: }
636:
637: <INITIAL,GOTDEFS>({ID}|{WORD}) {
638: /* a word */
639: if (!fill(sudoerstext, sudoersleng))
640: yyterminate();
641: LEXTRACE("WORD(5) ");
642: LEXRETURN(WORD);
643: }
644:
645: \( {
646: LEXTRACE("( ");
647: LEXRETURN('(');
648: }
649:
650: \) {
651: LEXTRACE(") ");
652: LEXRETURN(')');
653: }
654:
655: , {
656: LEXTRACE(", ");
657: LEXRETURN(',');
658: } /* return ',' */
659:
660: = {
661: LEXTRACE("= ");
662: LEXRETURN('=');
663: } /* return '=' */
664:
665: : {
666: LEXTRACE(": ");
667: LEXRETURN(':');
668: } /* return ':' */
669:
670: <*>!+ {
671: if (sudoersleng & 1) {
672: LEXTRACE("!");
673: LEXRETURN('!'); /* return '!' */
674: }
675: }
676:
677: <*>\n {
678: if (YY_START == INSTR) {
679: LEXTRACE("ERROR ");
680: LEXRETURN(ERROR); /* line break in string */
681: }
682: BEGIN INITIAL;
683: sudolineno++;
684: continued = false;
685: LEXTRACE("\n");
686: LEXRETURN(COMMENT);
687: } /* return newline */
688:
689: <*>[[:blank:]]+ { /* throw away space/tabs */
690: sawspace = true; /* but remember for fill_args */
691: }
692:
693: <*>\\[[:blank:]]*\n {
694: sawspace = true; /* remember for fill_args */
695: sudolineno++;
696: continued = true;
697: } /* throw away EOL after \ */
698:
699: <INITIAL,STARTDEFS,INDEFS>#(-[^\n0-9].*|[^\n0-9-].*)?\n? {
700: if (sudoerstext[sudoersleng - 1] == '\n') {
701: /* comment ending in a newline */
702: BEGIN INITIAL;
703: sudolineno++;
704: continued = false;
705: } else if (!feof(yyin)) {
706: LEXTRACE("ERROR ");
707: LEXRETURN(ERROR);
708: }
709: LEXTRACE("#\n");
710: LEXRETURN(COMMENT);
711: } /* comment, not uid/gid */
712:
713: <*>. {
714: LEXTRACE("ERROR ");
715: LEXRETURN(ERROR);
716: } /* parse error */
717:
718: <*><<EOF>> {
719: if (YY_START != INITIAL) {
720: BEGIN INITIAL;
721: LEXTRACE("ERROR ");
722: LEXRETURN(ERROR);
723: }
724: if (!pop_include())
725: yyterminate();
726: }
727:
728: %%
729: struct path_list {
730: SLIST_ENTRY(path_list) entries;
731: char *path;
732: };
733:
734: SLIST_HEAD(path_list_head, path_list);
735:
736: struct include_stack {
737: YY_BUFFER_STATE bs;
738: char *path;
739: struct path_list_head more; /* more files in case of includedir */
740: int lineno;
741: bool keepopen;
742: };
743:
744: /*
745: * Compare two struct path_list structs in reverse order.
746: */
747: static int
748: pl_compare(const void *v1, const void *v2)
749: {
750: const struct path_list * const *p1 = v1;
751: const struct path_list * const *p2 = v2;
752:
753: return strcmp((*p2)->path, (*p1)->path);
754: }
755:
756: static char *
757: switch_dir(struct include_stack *stack, char *dirpath)
758: {
759: DIR *dir;
760: unsigned int i, count = 0;
761: unsigned int max_paths = 32;
762: char *path = NULL;
763: struct dirent *dent;
764: struct stat sb;
765: struct path_list *pl, **paths = NULL;
766: debug_decl(switch_dir, SUDO_DEBUG_PARSER)
767:
768: if (!(dir = opendir(dirpath))) {
769: if (errno != ENOENT) {
770: warning("%s", dirpath);
771: sudoerserror(NULL);
772: }
773: goto done;
774: }
775: paths = malloc(sizeof(*paths) * max_paths);
776: if (paths == NULL) {
777: closedir(dir);
778: goto bad;
779: }
780: while ((dent = readdir(dir))) {
781: /* Ignore files that end in '~' or have a '.' in them. */
782: if (dent->d_name[0] == '\0' || dent->d_name[NAMLEN(dent) - 1] == '~'
783: || strchr(dent->d_name, '.') != NULL) {
784: continue;
785: }
786: if (asprintf(&path, "%s/%s", dirpath, dent->d_name) == -1) {
787: closedir(dir);
788: goto bad;
789: }
790: if (stat(path, &sb) != 0 || !S_ISREG(sb.st_mode)) {
791: efree(path);
792: path = NULL;
793: continue;
794: }
795: pl = malloc(sizeof(*pl));
796: if (pl == NULL)
797: goto bad;
798: pl->path = path;
799: if (count >= max_paths) {
800: struct path_list **tmp;
801: max_paths <<= 1;
802: tmp = realloc(paths, sizeof(*paths) * max_paths);
803: if (tmp == NULL) {
804: closedir(dir);
805: goto bad;
806: }
807: paths = tmp;
808: }
809: paths[count++] = pl;
810: path = NULL;
811: }
812: closedir(dir);
813:
814: if (count == 0)
815: goto done;
816:
817: /* Sort the list as an array in reverse order. */
818: qsort(paths, count, sizeof(*paths), pl_compare);
819:
820: /* Build up the list in sorted order. */
821: for (i = 0; i < count; i++) {
822: SLIST_INSERT_HEAD(&stack->more, paths[i], entries);
823: }
824:
825: /* Pull out the first element for parsing, leave the rest for later. */
826: pl = SLIST_FIRST(&stack->more);
827: SLIST_REMOVE_HEAD(&stack->more, entries);
828: path = pl->path;
829: efree(pl);
830: done:
831: efree(paths);
832: efree(dirpath);
833: debug_return_str(path);
834: bad:
835: for (i = 0; i < count; i++) {
836: efree(paths[i]->path);
837: efree(paths[i]);
838: }
839: efree(paths);
840: efree(dirpath);
841: efree(path);
842: debug_return_str(NULL);
843: }
844:
845: #define MAX_SUDOERS_DEPTH 128
846: #define SUDOERS_STACK_INCREMENT 16
847:
848: static size_t istacksize, idepth;
849: static struct include_stack *istack;
850: static bool keepopen;
851:
852: void
853: init_lexer(void)
854: {
855: struct path_list *pl;
856: debug_decl(init_lexer, SUDO_DEBUG_PARSER)
857:
858: while (idepth) {
859: idepth--;
860: while ((pl = SLIST_FIRST(&istack[idepth].more)) != NULL) {
861: SLIST_REMOVE_HEAD(&istack[idepth].more, entries);
862: efree(pl->path);
863: efree(pl);
864: }
865: efree(istack[idepth].path);
866: if (idepth && !istack[idepth].keepopen)
867: fclose(istack[idepth].bs->yy_input_file);
868: sudoers_delete_buffer(istack[idepth].bs);
869: }
870: efree(istack);
871: istack = NULL;
872: istacksize = idepth = 0;
873: sudolineno = 1;
874: keepopen = false;
875: sawspace = false;
876: continued = false;
877: prev_state = INITIAL;
878:
879: debug_return;
880: }
881:
882: static bool
883: _push_include(char *path, bool isdir)
884: {
885: struct path_list *pl;
886: FILE *fp;
887: debug_decl(_push_include, SUDO_DEBUG_PARSER)
888:
889: /* push current state onto stack */
890: if (idepth >= istacksize) {
891: struct include_stack *new_istack;
892:
893: if (idepth > MAX_SUDOERS_DEPTH) {
894: sudoerserror(N_("too many levels of includes"));
895: debug_return_bool(false);
896: }
897: istacksize += SUDOERS_STACK_INCREMENT;
898: new_istack = (struct include_stack *) realloc(istack,
899: sizeof(*istack) * istacksize);
900: if (new_istack == NULL) {
901: warning(NULL);
902: sudoerserror(NULL);
903: debug_return_bool(false);
904: }
905: istack = new_istack;
906: }
907: SLIST_INIT(&istack[idepth].more);
908: if (isdir) {
909: struct stat sb;
910: switch (sudo_secure_dir(path, sudoers_uid, sudoers_gid, &sb)) {
911: case SUDO_PATH_SECURE:
912: break;
913: case SUDO_PATH_MISSING:
914: debug_return_bool(false);
915: case SUDO_PATH_BAD_TYPE:
916: errno = ENOTDIR;
917: if (sudoers_warnings) {
918: warning("%s", path);
919: }
920: debug_return_bool(false);
921: case SUDO_PATH_WRONG_OWNER:
922: if (sudoers_warnings) {
923: warningx(U_("%s is owned by uid %u, should be %u"),
924: path, (unsigned int) sb.st_uid,
925: (unsigned int) sudoers_uid);
926: }
927: debug_return_bool(false);
928: case SUDO_PATH_WORLD_WRITABLE:
929: if (sudoers_warnings) {
930: warningx(U_("%s is world writable"), path);
931: }
932: debug_return_bool(false);
933: case SUDO_PATH_GROUP_WRITABLE:
934: if (sudoers_warnings) {
935: warningx(U_("%s is owned by gid %u, should be %u"),
936: path, (unsigned int) sb.st_gid,
937: (unsigned int) sudoers_gid);
938: }
939: debug_return_bool(false);
940: default:
941: /* NOTREACHED */
942: debug_return_bool(false);
943: }
944: if (!(path = switch_dir(&istack[idepth], path))) {
945: /* switch_dir() called sudoerserror() for us */
946: debug_return_bool(false);
947: }
948: while ((fp = open_sudoers(path, false, &keepopen)) == NULL) {
949: /* Unable to open path in includedir, go to next one, if any. */
950: efree(path);
951: if ((pl = SLIST_FIRST(&istack[idepth].more)) == NULL)
952: debug_return_bool(false);
953: SLIST_REMOVE_HEAD(&istack[idepth].more, entries);
954: path = pl->path;
955: efree(pl);
956: }
957: } else {
958: if ((fp = open_sudoers(path, true, &keepopen)) == NULL) {
959: /* The error was already printed by open_sudoers() */
960: sudoerserror(NULL);
961: debug_return_bool(false);
962: }
963: }
964: /* Push the old (current) file and open the new one. */
965: istack[idepth].path = sudoers; /* push old path */
966: istack[idepth].bs = YY_CURRENT_BUFFER;
967: istack[idepth].lineno = sudolineno;
968: istack[idepth].keepopen = keepopen;
969: idepth++;
970: sudolineno = 1;
971: sudoers = path;
972: sudoers_switch_to_buffer(sudoers_create_buffer(fp, YY_BUF_SIZE));
973:
974: debug_return_bool(true);
975: }
976:
977: static bool
978: pop_include(void)
979: {
980: struct path_list *pl;
981: FILE *fp;
982: debug_decl(pop_include, SUDO_DEBUG_PARSER)
983:
984: if (idepth == 0)
985: debug_return_bool(false);
986:
987: if (!keepopen)
988: fclose(YY_CURRENT_BUFFER->yy_input_file);
989: sudoers_delete_buffer(YY_CURRENT_BUFFER);
990: /* If we are in an include dir, move to the next file. */
991: while ((pl = SLIST_FIRST(&istack[idepth - 1].more)) != NULL) {
992: SLIST_REMOVE_HEAD(&istack[idepth - 1].more, entries);
993: fp = open_sudoers(pl->path, false, &keepopen);
994: if (fp != NULL) {
995: efree(sudoers);
996: sudoers = pl->path;
997: sudolineno = 1;
998: sudoers_switch_to_buffer(sudoers_create_buffer(fp, YY_BUF_SIZE));
999: efree(pl);
1000: break;
1001: }
1002: /* Unable to open path in include dir, go to next one. */
1003: efree(pl->path);
1004: efree(pl);
1005: }
1006: /* If no path list, just pop the last dir on the stack. */
1007: if (pl == NULL) {
1008: idepth--;
1009: sudoers_switch_to_buffer(istack[idepth].bs);
1010: efree(sudoers);
1011: sudoers = istack[idepth].path;
1012: sudolineno = istack[idepth].lineno;
1013: keepopen = istack[idepth].keepopen;
1014: }
1015: debug_return_bool(true);
1016: }
1017:
1018: static char *
1019: parse_include(char *base)
1020: {
1021: char *cp, *ep, *path, *pp;
1022: int dirlen = 0, len = 0, subst = 0;
1023: size_t shost_len = 0;
1024: debug_decl(parse_include, SUDO_DEBUG_PARSER)
1025:
1026: /* Pull out path from #include line. */
1027: cp = base + sizeof("#include");
1028: if (*cp == 'i')
1029: cp += 3; /* includedir */
1030: while (isblank((unsigned char) *cp))
1031: cp++;
1032: ep = cp;
1033: while (*ep != '\0' && !isspace((unsigned char) *ep)) {
1034: if (ep[0] == '%' && ep[1] == 'h') {
1035: shost_len = strlen(user_shost);
1036: len += shost_len - 2;
1037: subst = 1;
1038: }
1039: ep++;
1040: }
1041:
1042: /* Relative paths are located in the same dir as the sudoers file. */
1043: if (*cp != '/') {
1044: char *dirend = strrchr(sudoers, '/');
1045: if (dirend != NULL)
1046: dirlen = (int)(dirend - sudoers) + 1;
1047: }
1048:
1049: /* Make a copy of the fully-qualified path and return it. */
1050: len += (int)(ep - cp);
1051: path = pp = malloc(len + dirlen + 1);
1052: if (path == NULL) {
1053: warning(NULL);
1054: sudoerserror(NULL);
1055: debug_return_str(NULL);
1056: }
1057: if (dirlen) {
1058: memcpy(path, sudoers, dirlen);
1059: pp += dirlen;
1060: }
1061: if (subst) {
1062: /* substitute for %h */
1063: while (cp < ep) {
1064: if (cp[0] == '%' && cp[1] == 'h') {
1065: memcpy(pp, user_shost, shost_len);
1066: pp += shost_len;
1067: cp += 2;
1068: continue;
1069: }
1070: *pp++ = *cp++;
1071: }
1072: *pp = '\0';
1073: } else {
1074: memcpy(pp, cp, len);
1075: pp[len] = '\0';
1076: }
1077:
1078: /* Push any excess characters (e.g. comment, newline) back to the lexer */
1079: if (*ep != '\0')
1080: yyless((int)(ep - base));
1081:
1082: debug_return_str(path);
1083: }
1084:
1085: #ifdef TRACELEXER
1086: int
1087: sudoers_trace_print(const char *msg)
1088: {
1089: return fputs(msg, stderr);
1090: }
1091: #else
1092: int
1093: sudoers_trace_print(const char *msg)
1094: {
1095: static bool initialized;
1096: static struct lbuf lbuf;
1097:
1098: if (!initialized) {
1099: initialized = true;
1100: lbuf_init(&lbuf, NULL, 0, NULL, 0);
1101: }
1102:
1103: lbuf_append(&lbuf, "%s", msg);
1104: /* XXX - assumes a final newline */
1105: if (strchr(msg, '\n') != NULL)
1106: {
1107: sudo_debug_printf2(NULL, NULL, 0, SUDO_DEBUG_PARSER|SUDO_DEBUG_DEBUG,
1108: "%s:%d %s", sudoers, sudolineno, lbuf.buf);
1109: lbuf.len = 0;
1110: }
1111: return 0;
1112: }
1113: #endif /* TRACELEXER */
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>