2
0
mirror of https://gitlab.com/apparmor/apparmor synced 2025-08-22 01:57:43 +00:00

allow multiple profiles to be parsed from the command line

Signed-Off-By: Kees Cook <kees.cook@canonical.com>
This commit is contained in:
John Johansen 2009-07-24 07:35:39 +00:00
parent af902dddf1
commit 33d01a980a
5 changed files with 265 additions and 316 deletions

View File

@ -219,6 +219,9 @@ extern int yyparse(void);
extern void yyerror(char *msg, ...); extern void yyerror(char *msg, ...);
extern int yylex(void); extern int yylex(void);
/* parser_include.c */
extern char *basedir;
/* parser_regex.c */ /* parser_regex.c */
extern int process_regex(struct codomain *cod); extern int process_regex(struct codomain *cod);
extern int post_process_entry(struct cod_entry *entry); extern int post_process_entry(struct cod_entry *entry);

View File

@ -67,67 +67,16 @@ static char *path[MAX_PATH] = { NULL };
static int npath = 0; static int npath = 0;
static int fgetline(FILE * f, char *buffer, size_t len); 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 int stripcomment(char *s);
static char *stripblanks(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 /* default base directory is /etc/subdomain.d, it can be overriden
with the -b option. */ with the -b option. */
static char *basedir; char *basedir;
static char *default_basedir = "/etc/apparmor.d"; static char *default_basedir = "/etc/apparmor.d";
static char *old_basedir = "/etc/subdomain.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. */ /* set up basedir so that it can be overridden/used later. */
void init_base_dir(void) void init_base_dir(void)
@ -258,233 +207,24 @@ out:
} }
} }
const char incword[] = "include"; FILE *search_path(char *filename, char **fullpath)
/* 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 *newf = NULL; FILE *newf = NULL;
int retval = 0; char *buf = NULL;
char *buf; int i;
struct stat my_stat; for (i = 0; i < npath; i++) {
int err; if (asprintf(&buf, "%s/%s", path[i], filename) < 0) {
perror("asprintf");
if (*inc == '\"') { exit(1);
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;
} }
newf = fopen(buf, "r");
if (newf && fullpath) *fullpath = buf;
else free(buf);
buf = NULL;
if (newf)
break;
} }
return newf;
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;
} }
/* get a line from the file. If it is to long truncate it. */ /* get a line from the file. If it is to long truncate it. */

View File

@ -27,5 +27,6 @@ extern void init_base_dir(void);
extern void set_base_dir(char *dir); extern void set_base_dir(char *dir);
extern void parse_default_paths(void); extern void parse_default_paths(void);
extern int do_include_preprocessing(char *profilename); extern int do_include_preprocessing(char *profilename);
FILE *search_path(char *filename, char **fullpath);
#endif #endif

View File

@ -28,11 +28,19 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <libintl.h> #include <libintl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#define _(s) gettext(s) #define _(s) gettext(s)
#include "parser.h" #include "parser.h"
#include "parser_include.h"
#include "parser_yacc.h" #include "parser_yacc.h"
#ifdef PDEBUG
#undef PDEBUG
#endif
/* #define DEBUG */ /* #define DEBUG */
#ifdef DEBUG #ifdef DEBUG
#define PDEBUG(fmt, args...) printf("Lexer: " fmt, ## args) #define PDEBUG(fmt, args...) printf("Lexer: " fmt, ## args)
@ -43,6 +51,100 @@
int current_lineno = 1; 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 "^" UP "^"
@ -88,9 +190,39 @@ LT_EQUAL <=
%x ASSIGN_MODE %x ASSIGN_MODE
%x RLIMIT_MODE %x RLIMIT_MODE
%x CHANGE_PROFILE_MODE %x CHANGE_PROFILE_MODE
%x INCLUDE
%% %%
<INCLUDE>{
{WS}+ { /* Eat whitespace */ }
\<([^\> \t\n]+)\> { /* <filename> */
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);
}
}
<<EOF>> {
yypop_buffer_state();
if ( !YY_CURRENT_BUFFER ) yyterminate();
}
<SUB_NAME>{ <SUB_NAME>{
{ID}+ { {ID}+ {
/* Ugh, this is a gross hack. I used to use /* 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++; 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; } {END_OF_RULE} { return TOK_END_OF_RULE; }
{SEPERATOR} { {SEPERATOR} {

View File

@ -49,7 +49,7 @@
#define PCRE "pattern=pcre" #define PCRE "pattern=pcre"
#define AADFA "pattern=aadfa" #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) dump_vars || dump_expanded_vars)
const char *parser_title = "Novell/SUSE AppArmor parser"; 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; char *progname;
int option = OPTION_ADD; int option = OPTION_ADD;
int force_complain = 0; int opt_force_complain = 0;
int binary_input = 0; int binary_input = 0;
int names_only = 0; int names_only = 0;
int dump_vars = 0; int dump_vars = 0;
@ -70,16 +70,18 @@ int read_implies_exec = 0;
#endif #endif
char *subdomainbase = NULL; char *subdomainbase = NULL;
char *profilename;
char *match_string = NULL; char *match_string = NULL;
char *flags_string = NULL; char *flags_string = NULL;
int regex_type = AARE_DFA; int regex_type = AARE_DFA;
char *profile_namespace = NULL; char *profile_namespace = NULL;
int flag_changehat_version = FLAG_CHANGEHAT_1_5; int flag_changehat_version = FLAG_CHANGEHAT_1_5;
extern int current_lineno; extern int current_lineno;
/* per-profile settings */
int force_complain = 0;
char *profilename = NULL;
struct option long_options[] = { struct option long_options[] = {
{"add", 0, 0, 'a'}, {"add", 0, 0, 'a'},
{"binary", 0, 0, 'B'}, {"binary", 0, 0, 'B'},
@ -90,7 +92,6 @@ struct option long_options[] = {
{"replace", 0, 0, 'r'}, {"replace", 0, 0, 'r'},
{"reload", 0, 0, 'r'}, /* undocumented reload option == replace */ {"reload", 0, 0, 'r'}, /* undocumented reload option == replace */
{"version", 0, 0, 'v'}, {"version", 0, 0, 'v'},
{"preprocess", 0, 0, 'p'},
{"complain", 0, 0, 'C'}, {"complain", 0, 0, 'C'},
{"Complain", 0, 0, 'C'}, /* Erk, apparently documented as --Complain */ {"Complain", 0, 0, 'C'}, /* Erk, apparently documented as --Complain */
{"dump-variables", 0, 0, 'D'}, {"dump-variables", 0, 0, 'D'},
@ -125,7 +126,6 @@ static void display_usage(char *command)
"-R, --remove Remove apparmor definitions\n" "-R, --remove Remove apparmor definitions\n"
"-C, --Complain Force the profile into complain mode\n" "-C, --Complain Force the profile into complain mode\n"
"-B, --binary Input is precompiled profile\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" "-N, --names Dump names of profiles in input.\n"
"-S, --stdout Dump compiled profile to stdout\n" "-S, --stdout Dump compiled profile to stdout\n"
"-b n, --base n Set base dir and cwd\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(); display_version();
exit(0); exit(0);
break; break;
case 'p':
count++;
preprocess_only = 1;
break;
case 'I': case 'I':
add_search_dir(optarg); add_search_dir(optarg);
break; break;
@ -213,7 +209,7 @@ static int process_args(int argc, char *argv[])
binary_input =1; binary_input =1;
break; break;
case 'C': case 'C':
force_complain = 1; opt_force_complain = 1;
break; break;
case 'N': case 'N':
names_only = 1; names_only = 1;
@ -251,27 +247,14 @@ static int process_args(int argc, char *argv[])
} }
if (count > 1) { 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); progname);
goto abort; display_usage(progname);
exit(1);
} }
PDEBUG("optind = %d argc = %d\n", optind, argc); PDEBUG("optind = %d argc = %d\n", optind, argc);
if (optind < argc) { return optind;
/* 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);
} }
static inline char *try_subdomainfs_mountpoint(const char *mntpnt, static inline char *try_subdomainfs_mountpoint(const char *mntpnt,
@ -490,16 +473,83 @@ int process_binary(int option, char *profilename)
free(buffer); 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; return retval;
} }
void reset_parser(void)
{
free_aliases();
free_symtabs();
free_policies();
reset_regex();
}
int process_profile(int option, char *profilename) int process_profile(int option, char *profilename)
{ {
struct stat stat_text;
struct stat stat_bin;
int retval = 0; int retval = 0;
retval = do_include_preprocessing(profilename); /* per-profile states */
if (preprocess_only || retval != 0) force_complain = opt_force_complain;
return retval;
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(); retval = yyparse();
if (retval != 0) if (retval != 0)
@ -517,7 +567,7 @@ int process_profile(int option, char *profilename)
retval = post_process_policy(); retval = post_process_policy();
if (retval != 0) { if (retval != 0) {
PERROR(_("%s: Errors found in file. Aborting.\n"), progname); PERROR(_("%s: Errors found in file. Aborting.\n"), progname);
return retval; goto out;
} }
if (dump_vars) { if (dump_vars) {
@ -550,14 +600,15 @@ out:
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
int retval; int retval;
int option; int i;
int optind;
/* name of executable, for error reporting and usage display */ /* name of executable, for error reporting and usage display */
progname = argv[0]; progname = argv[0];
init_base_dir(); init_base_dir();
option = process_args(argc, argv); optind = process_args(argc, argv);
setlocale(LC_MESSAGES, ""); setlocale(LC_MESSAGES, "");
bindtextdomain(PACKAGE, LOCALEDIR); bindtextdomain(PACKAGE, LOCALEDIR);
@ -575,11 +626,26 @@ int main(int argc, char *argv[])
return retval; return retval;
} }
if (binary_input) { if (!binary_input) parse_default_paths();
retval = process_binary(option, profilename);
} else { retval = 0;
parse_default_paths(); for (i = optind; retval == 0 && i <= argc; i++) {
retval = process_profile(option, profilename); 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; return retval;