apparmor/parser/parser_include.c
Steve Beattie 398ef08bf4 This patch adds support for including all the files within a directory
by naming the directory (e.g. #include </etc/apparmor.d/abstractions>).
It will skip over dotfiles and as well as subdirectories. It is intended
to make scripting and packaging easier, as the including profile will
not need to know explicitly all of the names to include.

Long term, rather than hardcode that dotfiles will be skipped, it should
be configurable via /etc/apparmor.d/subdomain what patterns of files
should be skipped; genprof/logprof should also honor this setting. The
code could be reused as I'd like to make the parser just take a directory
on the command line rather than being fed profiles one at a time; again
it would skip files based on these same patterns.

This patch also eliminates some dead code in the include processing, as
well as replaces a couple of undersized fixed-size buffer (PATH_MAX is
4095 on linux, buffer is not overflowable due to use of strncpy/snprintf)
with dynamically allocated ones.
2006-10-10 21:36:10 +00:00

522 lines
11 KiB
C

/* $Id$ */
/*
* Copyright (c) 2004, 2005 NOVELL (All rights reserved)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, contact Novell, Inc.
*/
/* Handle subdomain includes, as a straight forward preprocessing phase.
While we are at it we will strip comments. Why? because it made it
easier.
We support 2 types of includes
#include <name> which searches for the first occurance of name in the
subdomain directory path.
#include "name" which will search for a relative or absolute pathed
file
-p : preprocess only. Dump output to stdout
-I path : add a path to be search by #include < >
-b path : set the base path to something other than /etc/subdomain.d
*/
#define _GNU_SOURCE /* for strndup() */
#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <libintl.h>
#include <dirent.h>
#include "parser.h"
#include "parser_include.h"
#define _(s) gettext(s)
/* An array of search directories, I sure hope 100's enough */
#define MAX_PATH 100
/* maximum depth of nesting */
#define MAX_NEST_LEVEL 100
/* Location of the subdomain.conf file */
#ifdef SUBDOMAIN_CONFDIR
#define SUBDOMAIN_CONF SUBDOMAIN_CONFDIR "/subdomain.conf"
#else /* !defined SUBDOMAIN_CONFDIR */
#define SUBDOMAIN_CONF "/etc/subdomain.conf"
#endif /* SUBDOMAIN_CONFDIR */
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;
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)
{
int rc;
struct stat sbuf;
/* basedir should always start out NULL; if not, something's
* wrong.*/
assert(basedir == NULL);
rc = stat(default_basedir, &sbuf);
if (rc == 0 && S_ISDIR(sbuf.st_mode)) {
basedir = default_basedir;
return;
}
rc = stat(old_basedir, &sbuf);
if (rc == 0 && S_ISDIR(sbuf.st_mode)) {
basedir = old_basedir;
return;
}
}
/* Set the base dir. Used to change default path for relative includes */
void set_base_dir(char *dir)
{
char *t;
int i, rc;
struct stat sbuf;
t = strndup(dir, PATH_MAX);
if (!t) {
PERROR(_("Error: Out of memory.\n"));
return;
}
/*strip trailing /'s */
for (i = strlen(t) - 1; i >= 0 && t[i] == '/'; i--)
t[i] = 0;
rc = stat(t, &sbuf);
if (rc == -1 || !S_ISDIR(sbuf.st_mode)) {
PERROR(_("Error: basedir %s is not a directory, skipping.\n"), t);
free(t);
return;
}
basedir = t;
return;
}
/* Add a directory to the search path. */
int add_search_dir(char *dir)
{
char *t;
if (npath >= MAX_PATH) {
PERROR(_("Error: Could not add directory %s to search path.\n"),
dir);
return 0;
}
if (!dir || strlen(dir) <= 0)
return 1;
t = malloc(strlen(dir) + 1);
if (t == NULL) {
PERROR(_("Error: Could not allocate memory.\n"));
return 0;
}
strcpy(t, dir);
/*strip trailing /'s */
while (t[strlen(t) - 1] == '/')
t[strlen(t) - 1] = 0;
path[npath] = t;
npath++;
return 1;
}
/* Parse Subdomain.conf and put the default dirs in place.
subdomain.conf is a shell sourcable file
we only parse entries starting with
SUBDOMAIN_PATH=
if there are multiple entries with SUBDOMAIN_PATH=
each will get added.
SUBDOMAIN_PATH=/etc/subdomain.d:/etc/subdomain.d/include
is the same as
SUBDOMAIN_PATH=/etc/subdomain.d
SUBDOMAIN_PATH=/etc/subdomain.d/include */
void parse_default_paths(void)
{
FILE *f;
char buf[1024];
char *t, *s;
int saved_npath = npath;
f = fopen(SUBDOMAIN_CONF, "r");
if (f == NULL)
goto out;
memset(buf, 0, sizeof(buf));
while (fgetline(f, buf, 1024)) {
if (stripcomment(buf) && (t = strstr(buf, "SUBDOMAIN_PATH="))) {
t += 15;
/* handle : seperating path elements */
do {
s = strchr(t, ':');
if (s)
*s = 0;
if (!add_search_dir(stripblanks(t)))
break;
if (s)
t = s + 1;
} while (s != NULL);
}
}
fclose(f);
/* if subdomain.conf doesn't set a base search dir set it to this */
out:
if (npath - saved_npath == 0) {
add_search_dir(basedir);
}
}
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 *newf = NULL;
int retval = 0;
char *buf;
struct stat my_stat;
int err;
if (*inc == '\"') {
buf = strdup(inc + 1);
newf = fopen(buf, "r");
} else {
int i;
for (i = 0; i < npath; i++) {
asprintf(&buf, "%s/%s", path[i], inc + 1);
newf = fopen(buf, "r");
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 = fdopendir(fileno(newf));
struct dirent *dirent;
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. */
static int fgetline(FILE * f, char *buffer, size_t len)
{
char *b = buffer;
int c;
while (((c = fgetc(f)) != EOF) && (c != '\n')
&& (strlen(buffer) < len - 1)) {
*b = c;
b++;
}
*b = '\0';
if (c != EOF)
return 1;
return 0;
}
/* If there is a comment null terminate the string,
return strlen of the stripped string*/
static int stripcomment(char *s)
{
char *t = s;
while (*s != '#' && *s != 0)
s++;
*s = 0;
return strlen(t);
}
static char *stripblanks(char *s)
{
char *c;
while (isspace(*s))
s++;
c = s;
while (!isspace(*s) && *s != 0)
s++;
*s = 0;
return c;
}