diff --git a/parser/parser.h b/parser/parser.h index 07315b6b5..c3db29068 100644 --- a/parser/parser.h +++ b/parser/parser.h @@ -219,6 +219,9 @@ extern int yyparse(void); extern void yyerror(char *msg, ...); extern int yylex(void); +/* parser_include.c */ +extern char *basedir; + /* parser_regex.c */ extern int process_regex(struct codomain *cod); extern int post_process_entry(struct cod_entry *entry); diff --git a/parser/parser_include.c b/parser/parser_include.c index 1a6d83924..8fd7a0ad8 100644 --- a/parser/parser_include.c +++ b/parser/parser_include.c @@ -67,67 +67,16 @@ static char *path[MAX_PATH] = { NULL }; static int npath = 0; static int fgetline(FILE * f, char *buffer, size_t len); -static int getincludestr(char **inc, int c, FILE *f, int line, char *name, - FILE *out); static int stripcomment(char *s); static char *stripblanks(char *s); -static int preprocess(FILE *f, char *name, FILE * out, int nest); - -int preprocess_only; /* default base directory is /etc/subdomain.d, it can be overriden with the -b option. */ -static char *basedir; +char *basedir; static char *default_basedir = "/etc/apparmor.d"; static char *old_basedir = "/etc/subdomain.d"; -/* start parsing. */ -int do_include_preprocessing(char *profilename) -{ - int retval = 0; - FILE *tmp, *profile = NULL; - - if (profilename) { - profile = fopen(profilename, "r"); - if (!profile) { - PERROR(_("Error: Could not read profile %s: %s.\n"), - profilename, strerror(errno)); - exit(errno); - } - } else { - profile = stdin; - } - - /* Change to the base dir */ - chdir(basedir); - - if (preprocess_only) { - retval = preprocess(profile, profilename ? profilename : "stdin", - stdout, 0); - goto out; - } - - tmp = tmpfile(); - if (!tmp) { - PERROR(_("Error: Could not allocate temporary file.\n")); - exit(10); - } - - retval = preprocess(profile, profilename ? profilename : "stdin", - tmp, 0); - - rewind(tmp); - - dup2(fileno(tmp), 0); /* stdin */ - fclose(tmp); - -out: - if (profilename) - fclose(profile); - - return retval; -} /* set up basedir so that it can be overridden/used later. */ void init_base_dir(void) @@ -258,233 +207,24 @@ out: } } -const char incword[] = "include"; - -/* getincludestr: - * returns !0 if error occurred - * include string (or not) is returned in 'inc' - */ -static int getincludestr(char **inc, int c, FILE *f, int line, char *name, - FILE *out) -{ - char *b; - size_t i = 0, a; - int d; - int retval = 0; - - *inc = NULL; - - if (c != '#') - return retval; - - /* we either have a comment or an include, either process the include - or strip the comment to the eol. Leave the eol char so line count - gets properly incremented. */ - - for (i = 0; i < strlen(incword); i++) { - c = fgetc(f); - if (c == EOF || c == '\n' || c != incword[i]) { - ungetc(c, f); - goto comment; - } - } - - /* found "#include" now search for the file name to include */ - b = malloc(2048); - if (!b) { - PERROR(_("Error: Could not allocate buffer for include at line %d in %s.\n"), - line, name); - retval = 1; - goto comment; - } - - c = fgetc(f); - if (!isspace(c)) { - ungetc(c, f); - goto comment; - } - - while ((c = fgetc(f)) != EOF && c != '\n' && isspace(c)) - /* eat whitespace */ ; - if (c != '\"' && c != '<') { - free(b); - PERROR(_("Error: Bad include at line %d in %s.\n"), line, name); - if (c == '\n') - ungetc(c, f); - retval = 1; - goto comment; - } - - b[0] = c; - i = 1; - while ((d = fgetc(f)) != EOF && d != '\n' - && d != (c == '<' ? '>' : '\"') && i < 2048) - b[i++] = d; - - if (d == (c == '<' ? '>' : '\"')) { - b[i] = 0; - *inc = b; - return retval; - } - - free(b); - PERROR(_("Error: Bad include at line %d in %s.\n"), line, name); - ungetc(d, f); - retval = 1; - /* fall through to comment - this makes trailing stuff a comment */ - -comment: - fputc('#', out); - for (a = 0; a < i; a++) { - fputc(incword[a], out); - } - while ((c = fgetc(f)) != EOF && c != '\n') - fputc(c, out); - if (c == '\n') - ungetc(c, f); - - return retval; -} - -/* Find the include file or directory by searching the path. */ -static int process_include(char *inc, char *name, int line, FILE *out, int nest) +FILE *search_path(char *filename, char **fullpath) { FILE *newf = NULL; - int retval = 0; - char *buf; - struct stat my_stat; - int err; - - if (*inc == '\"') { - buf = strdup(inc + 1); - if (buf) - newf = fopen(buf, "r"); - } else { - int i; - for (i = 0; i < npath; i++) { - if (asprintf(&buf, "%s/%s", path[i], inc + 1) != -1) { - newf = fopen(buf, "r"); - if (newf) - break; - free(buf); - } - buf = NULL; + char *buf = NULL; + int i; + for (i = 0; i < npath; i++) { + if (asprintf(&buf, "%s/%s", path[i], filename) < 0) { + perror("asprintf"); + exit(1); } + newf = fopen(buf, "r"); + if (newf && fullpath) *fullpath = buf; + else free(buf); + buf = NULL; + if (newf) + break; } - - if (!newf) { - PERROR(_("Error: #include %s%c not found at line %d in %s.\n"), - inc, - *inc == '<' ? '>' : '\"', - line, - name); - retval = 1; - goto out; - } - - err = fstat(fileno(newf), &my_stat); - if (err) { - retval = errno; - goto out; - } - - if (S_ISREG(my_stat.st_mode)) { - err = preprocess(newf, inc + 1, out, nest + 1); - if (err) - retval = err; - goto out; - } - - if (S_ISDIR(my_stat.st_mode)) { - DIR *dir = NULL; - struct dirent *dirent; - - /* XXX - fdopendir not available in glibc < 2.4 */ - /* dir = fdopendir(fileno(newf)); */ - fclose(newf); - dir = opendir(buf); - if (!dir) { - retval = 1; - goto out; - } - - while ((dirent = readdir(dir)) != NULL) { - char *dirbuf; - /* skip dotfiles. */ - if (dirent->d_name[0] == '.') - continue; - asprintf(&dirbuf, "%s/%s", buf, dirent->d_name); - err = stat(dirbuf, &my_stat); - if (err) { - retval = errno; - free(dirbuf); - goto out; - } - - if (S_ISREG(my_stat.st_mode)) { - newf = fopen(dirbuf, "r"); - if (newf) { - err = preprocess(newf, inc + 1, out, nest + 1); - if (err) - retval = err; - fclose(newf); - } else { - retval = errno; - } - } - free(dirbuf); - } - newf = NULL; - closedir(dir); - } -out: - if (buf) - free(buf); - if (newf) - fclose(newf); - return retval; -} - -static int preprocess(FILE * f, char *name, FILE * out, int nest) -{ - int line = 1; - int c; - int retval = 0; - char *inc = NULL; - char *cwd; - - if (nest > MAX_NEST_LEVEL) { - PERROR(_("Error: Exceeded %d levels of includes. Not processing %s include.\n"), - MAX_NEST_LEVEL, name); - return 1; - } - - if (nest == 0) { - fprintf(out, "\n#source %s\n", name); - } else { - fprintf(out, "\n#included %s\n", name); - } - - while ((c = fgetc(f)) != EOF) { - int err = getincludestr(&inc, c, f, line, name, out); - if (err) - retval = err; - if (inc) { - cwd = get_current_dir_name(); - err = process_include(inc, name, line, out, nest); - if (err) - retval = err; - chdir(cwd); - free(cwd); - free(inc); - } else { - if (c != '#') - fputc(c, out); - if (c == '\n') - line++; - } - } - return retval; + return newf; } /* get a line from the file. If it is to long truncate it. */ diff --git a/parser/parser_include.h b/parser/parser_include.h index 9a7f6fb78..5634768bb 100644 --- a/parser/parser_include.h +++ b/parser/parser_include.h @@ -27,5 +27,6 @@ extern void init_base_dir(void); extern void set_base_dir(char *dir); extern void parse_default_paths(void); extern int do_include_preprocessing(char *profilename); +FILE *search_path(char *filename, char **fullpath); #endif diff --git a/parser/parser_lex.l b/parser/parser_lex.l index f04089e8d..824343519 100644 --- a/parser/parser_lex.l +++ b/parser/parser_lex.l @@ -28,11 +28,19 @@ #include #include #include +#include +#include +#include +#include #define _(s) gettext(s) #include "parser.h" +#include "parser_include.h" #include "parser_yacc.h" +#ifdef PDEBUG +#undef PDEBUG +#endif /* #define DEBUG */ #ifdef DEBUG #define PDEBUG(fmt, args...) printf("Lexer: " fmt, ## args) @@ -43,6 +51,100 @@ int current_lineno = 1; +struct ignored_suffix_t { + char * text; + int len; + int silent; +}; + +struct ignored_suffix_t ignored_suffixes[] = { + /* Debian packging files, which are in flux during install + should be silently ignored. */ + { ".dpkg-new", 9, 1 }, + { ".dpkg-old", 9, 1 }, + { ".dpkg-dist", 10, 1 }, + /* RPM packaging files have traditionally not been silently + ignored */ + { ".rpmnew", 7, 0 }, + { ".rpmsave", 8, 0 }, + /* Backup files should be mentioned */ + { "~", 1, 0 }, + { NULL, 0, 0 } +}; + +void include_filename(char *filename, int search) +{ + FILE *include_file = NULL; + struct stat my_stat; + char *fullpath = NULL; + + if (search) include_file = search_path(filename, &fullpath); + else { + fullpath = strdup(filename); + include_file = fopen(fullpath, "r"); + } + + if (!include_file) yyerror(_("Could not open '%s'"), fullpath); + + if (fstat(fileno(include_file), &my_stat)) + yyerror(_("fstat failed for '%s'"), fullpath); + + if (S_ISREG(my_stat.st_mode)) { + yyin = include_file; + PDEBUG("Opened include \"%s\"\n", fullpath); + yypush_buffer_state(yy_create_buffer( yyin, YY_BUF_SIZE )); + } + + if (S_ISDIR(my_stat.st_mode)) { + DIR *dir = NULL; + char *dirent_path = NULL; + struct dirent *dirent; + + PDEBUG("Opened include directory \"%s\"\n", fullpath); + if (!(dir = opendir(fullpath))) + yyerror(_("opendir failed '%s'"), fullpath); + fclose(include_file); + include_file = NULL; + + while ((dirent = readdir(dir)) != NULL) { + int name_len; + struct ignored_suffix_t *suffix; + /* skip dotfiles silently. */ + if (dirent->d_name[0] == '.') + continue; + + if (dirent_path) free(dirent_path); + if (asprintf(&dirent_path, "%s/%s", fullpath, dirent->d_name)<0) + yyerror("Out of memory"); + + name_len = strlen(dirent->d_name); + /* skip blacklisted suffixes */ + for (suffix = ignored_suffixes; suffix->text; suffix++) { + char *found; + if ( (found = strstr(dirent->d_name, suffix->text)) && + found - dirent->d_name + suffix->len == name_len ) { + name_len = 0; + if (!suffix->silent) + PERROR("Ignoring: '%s'\n", dirent_path); + break; + } + } + if (!name_len) continue; + + if (stat(dirent_path, &my_stat)) + yyerror(_("stat failed for '%s'"), dirent_path); + if (S_ISREG(my_stat.st_mode)) { + if (!(yyin = fopen(dirent_path,"r"))) + yyerror(_("Could not open '%s'"), filename); + PDEBUG("Opened include \"%s\"\n", filename); + yypush_buffer_state(yy_create_buffer( yyin, YY_BUF_SIZE )); + } + } + if (dirent_path) free(dirent_path); + closedir(dir); + } +} + %} UP "^" @@ -88,9 +190,39 @@ LT_EQUAL <= %x ASSIGN_MODE %x RLIMIT_MODE %x CHANGE_PROFILE_MODE +%x INCLUDE %% +{ + {WS}+ { /* Eat whitespace */ } + \<([^\> \t\n]+)\> { /* */ + char *filename = strdup(yytext); + filename[strlen(filename)-1]='\0'; + include_filename(filename+1, 1); + free(filename); + BEGIN(INITIAL); + } + + \"([^\" \t\n]+)\" { /* "filename" */ + char *filename = strdup(yytext); + filename[strlen(filename)-1]='\0'; + include_filename(filename+1, 0); + free(filename); + BEGIN(INITIAL); + } + + [^\<\>\"{WS}]+ { /* filename */ + include_filename(yytext, 0); + BEGIN(INITIAL); + } +} + +<> { + yypop_buffer_state(); + if ( !YY_CURRENT_BUFFER ) yyterminate(); +} + { {ID}+ { /* Ugh, this is a gross hack. I used to use @@ -279,11 +411,18 @@ LT_EQUAL <= } } -#.*\n { /* Comment - ignore */ +#include/.*\r?\n { /* include */ + PDEBUG("Matched #include\n"); current_lineno++; - PDEBUG("Line no++: %d\n", current_lineno); + BEGIN(INCLUDE); } +#.*\r?\n { /* normal comment */ + PDEBUG("comment(%d): %s\n", current_lineno, yytext); + current_lineno++; + BEGIN(INITIAL); +} + {END_OF_RULE} { return TOK_END_OF_RULE; } {SEPERATOR} { diff --git a/parser/parser_main.c b/parser/parser_main.c index 4305dc481..618184878 100644 --- a/parser/parser_main.c +++ b/parser/parser_main.c @@ -49,7 +49,7 @@ #define PCRE "pattern=pcre" #define AADFA "pattern=aadfa" -#define UNPRIVILEGED_OPS (debug || preprocess_only || option == OPTION_STDOUT || names_only || \ +#define UNPRIVILEGED_OPS (debug || option == OPTION_STDOUT || names_only || \ dump_vars || dump_expanded_vars) const char *parser_title = "Novell/SUSE AppArmor parser"; @@ -57,7 +57,7 @@ const char *parser_copyright = "Copyright (C) 1999, 2000, 2003, 2004, 2005, 2006 char *progname; int option = OPTION_ADD; -int force_complain = 0; +int opt_force_complain = 0; int binary_input = 0; int names_only = 0; int dump_vars = 0; @@ -70,16 +70,18 @@ int read_implies_exec = 0; #endif char *subdomainbase = NULL; -char *profilename; char *match_string = NULL; char *flags_string = NULL; int regex_type = AARE_DFA; char *profile_namespace = NULL; int flag_changehat_version = FLAG_CHANGEHAT_1_5; - extern int current_lineno; +/* per-profile settings */ +int force_complain = 0; +char *profilename = NULL; + struct option long_options[] = { {"add", 0, 0, 'a'}, {"binary", 0, 0, 'B'}, @@ -90,7 +92,6 @@ struct option long_options[] = { {"replace", 0, 0, 'r'}, {"reload", 0, 0, 'r'}, /* undocumented reload option == replace */ {"version", 0, 0, 'v'}, - {"preprocess", 0, 0, 'p'}, {"complain", 0, 0, 'C'}, {"Complain", 0, 0, 'C'}, /* Erk, apparently documented as --Complain */ {"dump-variables", 0, 0, 'D'}, @@ -125,7 +126,6 @@ static void display_usage(char *command) "-R, --remove Remove apparmor definitions\n" "-C, --Complain Force the profile into complain mode\n" "-B, --binary Input is precompiled profile\n" - "-p, --preprocess Dump profiles with includes expanded\n" "-N, --names Dump names of profiles in input.\n" "-S, --stdout Dump compiled profile to stdout\n" "-b n, --base n Set base dir and cwd\n" @@ -199,10 +199,6 @@ static int process_args(int argc, char *argv[]) display_version(); exit(0); break; - case 'p': - count++; - preprocess_only = 1; - break; case 'I': add_search_dir(optarg); break; @@ -213,7 +209,7 @@ static int process_args(int argc, char *argv[]) binary_input =1; break; case 'C': - force_complain = 1; + opt_force_complain = 1; break; case 'N': names_only = 1; @@ -251,27 +247,14 @@ static int process_args(int argc, char *argv[]) } if (count > 1) { - PERROR("%s: Too many options given on the command line.\n", + PERROR("%s: Too many actions given on the command line.\n", progname); - goto abort; + display_usage(progname); + exit(1); } PDEBUG("optind = %d argc = %d\n", optind, argc); - if (optind < argc) { - /* we only support one profile at a time */ - if (argc - optind == 1) { - PDEBUG("Using profile in '%s'\n", argv[optind]); - profilename = strndup(argv[optind], PATH_MAX); - } else { - goto abort; - } - } - - return option; - -abort: - display_usage(progname); - exit(1); + return optind; } static inline char *try_subdomainfs_mountpoint(const char *mntpnt, @@ -490,16 +473,83 @@ int process_binary(int option, char *profilename) free(buffer); + if (!conf_quiet) { + switch (option) { + case OPTION_ADD: + printf(_("Cached load succeeded for \"%s\".\n"), + profilename ? profilename : "stdin"); + break; + case OPTION_REPLACE: + printf(_("Cached reload succeeded for \"%s\".\n"), + profilename ? profilename : "stdin"); + break; + default: + break; + } + } + return retval; } +void reset_parser(void) +{ + free_aliases(); + free_symtabs(); + free_policies(); + reset_regex(); +} + int process_profile(int option, char *profilename) { + struct stat stat_text; + struct stat stat_bin; int retval = 0; - retval = do_include_preprocessing(profilename); - if (preprocess_only || retval != 0) - return retval; + /* per-profile states */ + force_complain = opt_force_complain; + + if ( profilename ) { + if ( !(yyin = fopen(profilename, "r")) ) { + PERROR(_("Error: Could not read profile %s: %s.\n"), + profilename, strerror(errno)); + exit(errno); + } + } + else { + PERROR("%s: cannot disable or force-complain via stdin\n", progname); + } + + if ( profilename && option != OPTION_REMOVE ) { + /* make decisions about disabled or complain-mode profiles */ + char *target = NULL; + char *basename = strrchr(profilename, '/'); + if (basename) basename++; + else basename = profilename; + + if (asprintf(&target, "%s/%s/%s", basedir, "disable", basename)<0) { + perror("asprintf"); + exit(1); + } + if (access(target, R_OK) == 0) { + PERROR("Skipped: %s\n", target); + free(target); + goto out; + } + free(target); + + if (asprintf(&target, "%s/%s/%s", basedir, "force-complain", basename)<0) { + perror("asprintf"); + exit(1); + } + if (access(target, R_OK) == 0) { + PERROR("Warning: found %s, forcing complain mode\n", target); + force_complain = 1; + } + free(target); + } + + if (yyin) yyrestart(yyin); + reset_parser(); retval = yyparse(); if (retval != 0) @@ -517,7 +567,7 @@ int process_profile(int option, char *profilename) retval = post_process_policy(); if (retval != 0) { PERROR(_("%s: Errors found in file. Aborting.\n"), progname); - return retval; + goto out; } if (dump_vars) { @@ -550,14 +600,15 @@ out: int main(int argc, char *argv[]) { int retval; - int option; + int i; + int optind; /* name of executable, for error reporting and usage display */ progname = argv[0]; init_base_dir(); - option = process_args(argc, argv); + optind = process_args(argc, argv); setlocale(LC_MESSAGES, ""); bindtextdomain(PACKAGE, LOCALEDIR); @@ -575,11 +626,26 @@ int main(int argc, char *argv[]) return retval; } - if (binary_input) { - retval = process_binary(option, profilename); - } else { - parse_default_paths(); - retval = process_profile(option, profilename); + if (!binary_input) parse_default_paths(); + + retval = 0; + for (i = optind; retval == 0 && i <= argc; i++) { + if (i < argc && !(profilename = strdup(argv[i]))) { + perror("strdup"); + return -1; + } + /* skip stdin if we've seen other command line arguments */ + if (i == argc && optind != argc) + continue; + + if (binary_input) { + retval = process_binary(option, profilename); + } else { + retval = process_profile(option, profilename); + } + + if (profilename) free(profilename); + profilename = NULL; } return retval;