diff --git a/refpolicy/Makefile b/refpolicy/Makefile index b14be0a8..66cd7df6 100644 --- a/refpolicy/Makefile +++ b/refpolicy/Makefile @@ -58,6 +58,7 @@ GENHOMEDIRCON := $(SBINDIR)/genhomedircon CFLAGS := -Wall SUPPORT := support +GENXML := $(SUPPORT)/segenxml.py GENDOC := $(SUPPORT)/sedoctool.py FCSORT := $(SUPPORT)/fc_sort SETTUN := $(SUPPORT)/set_tunables @@ -363,16 +364,8 @@ $(POLXML): $(ALL_INTERFACES) @echo "Creating $@" @mkdir -p tmp $(QUIET) echo '' > $@ - $(QUIET) echo '' >> $@ - $(QUIET) echo "" >> $@ -# do all modules, even disabled ones: - $(QUIET) for i in $(ALL_LAYERS); do \ - cat $$i/$(LAYERXML) >> $@ ;\ - egrep -h "^##[[:blank:]]" $$i/*.if | sed -e 's/^##[[:blank:]]//g' >> $@ ;\ - echo "" >> $@;\ - done - $(QUIET) egrep -h "^##[[:blank:]]" $(GLOBALTUN) | sed -e 's/^##[[:blank:]]//g' >> $@ - $(QUIET) echo "" >> $@ + $(QUIET) echo '' >> $@ + $(QUIET) $(GENXML) -w -m $(LAYERXML) -t $(GLOBALTUN) $(ALL_LAYERS) >> $@ $(QUIET) if test -x $(XMLLINT) && test -f $(XMLDTD); then \ $(XMLLINT) --noout --dtdvalid $(XMLDTD) $@ ;\ fi diff --git a/refpolicy/support/segenxml.py b/refpolicy/support/segenxml.py new file mode 100755 index 00000000..a7f547a6 --- /dev/null +++ b/refpolicy/support/segenxml.py @@ -0,0 +1,412 @@ +#!/usr/bin/python + +# Author: Donald Miner +# +# Copyright (C) 2003 - 2005 Tresys Technology, LLC +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 2. + +""" + This script generates XML documentation information for layers specified + by the user. +""" + +import sys +import os +import glob + + +# GLOBALS +class dec_style: + ''' + "Declaration Style" + Specifies the syntax of a declaration. Intended to be used with + getParams(). + ''' + + # Example of a line: foo(bar,one,two); + # A style that would fit this: dec_style("foo(",3,",",");") + # "foo(" - the opening of it, ends at the begining of the first param. + # 3 - the number of parameters. + # "," - the delimeter to parse apart parameters. + # ");" - the end of the declaration statement. + + def __init__(self,open_str,params,delim,close_str): + self.open_str = open_str + self.params = params + self.delim = delim + self.close_str = close_str + + +INTERFACE = dec_style("interface(`",1,None,"'") +TUNABLE = dec_style("gen_tunable(",2,",",")") +# boolean FIXME: may have to change in the future. +BOOLEAN = dec_style("gen_bool(",2,",",")") + + +# Default values of command line arguments. +directory = "./" +warn = False +meta = "metadata" +layers = [] +tunable_files = [] + + + +# FUNCTIONS +def getFileBase(file_name): + ''' + Return the file base, the file name without the extension. + ''' + + # Start from the end of the string and stop when the first '.' is + # encountered, ignores hidden files denoted by a leading ','. + for i in range(1,len(file_name)-1): + if file_name[-i] == '.': + return file_name[:-i] + + return file_name + +def getXMLComment(line): + ''' + Returns the XML comment, (removes "## " from the front of the line). + Returns False if the line is not an XML comment. + ''' + + for i in range(0,len(line)-1): + # Check if the first 3 characters are "## " + if line[i:i+3] in ("## ", "##\t"): + # The chars before '#' in the line must be whitespace. + if i > 0 and not line[0:i-1].isspace(): + return False + else: + return line[i+3:] + + # No XML comment. + return False + +def getParams(line, style): + ''' + Returns a list of items, containing the values of each parameter. + ''' + + # Clean out whitespace. + temp_line = line.strip() + + # Check to see if the string begins with the specified opening + # string specified by style. + if temp_line[0:len(style.open_str)] == style.open_str: + temp_line = temp_line[len(style.open_str):].strip() + else: + return False + + # If there is a delimeter. + if style.delim: + temp_line = temp_line.split(style.delim) + else: + temp_line = [temp_line] + + # Only interested in a sertain number of tokens, specified by style. + temp_line = temp_line[:style.params] + + # Remove the end of the declaration, specified by style. + end = temp_line[len(temp_line)-1].find(style.close_str) + if end == -1: + warning("line \"%s\" may be syntactically incorrect"\ + % line.strip()) + return False + + temp_line[len(temp_line)-1] = temp_line[len(temp_line)-1][:end] + + # Remove whitespace + for i in range(0,len(temp_line)-1): + temp_line[i] = temp_line[i].strip() + + return temp_line + +def getModuleXML(file_name): + ''' + Returns the XML data for a module in a list, one line per list item. + ''' + + # Try to open the file, if it cant, just ignore it. + try: + module_file = open(file_name, "r") + module_code = module_file.readlines() + module_file.close() + except: + warning("cannot open file %s for read, skipping" % file_name) + return [] + + module_buf = [] + + # Infer the module name. + module_buf.append("\n" % getFileBase(file_name)) + + temp_buf = [] + + # Phases: find header - looking for the header of the file. + # get header - get the header comments and stop when first + # whitespace is encountered. + # find interface - looking for interfaces to get info for. + phase = "find header" + + # Go line by line and figure out what to do with it. + for line in module_code: + # In this phase, whitespace and stray code is ignored at the + # top fo the file. + if phase == "find header": + if line.isspace(): + continue + # Once a comment is encountered, start trying to get the + # header documentation. + elif getXMLComment(line): + phase = "get header" + # If an interface is found, there is no header, and no + # documentation for the interface. + elif getParams(line,INTERFACE): + phase = "find interface" + + # In this phase, XML comments are being retrieved for the file. + if phase == "get header": + if getXMLComment(line): + temp_buf.append(getXMLComment(line)) + continue + # If the line is whitespace, the file header is over, + # continue on to find interfaces. + elif line.isspace(): + module_buf += temp_buf + temp_buf = [] + phase = "find interface" + continue + # Oops! The comments we have been getting weren't part + # of the header so attribute them to an interface + # instead. + elif getParams(line,INTERFACE): + phase = "find interface" + + # In this phase, XML comments are being attributed + if phase == "find interface": + if getXMLComment(line): + temp_buf.append(getXMLComment(line)) + continue + # If the line is the declaration of a interface, + # infer the interface name and add all the comments + # to the main buffer. + elif getParams(line,INTERFACE): + module_buf.append("\n"\ + % getParams(line,INTERFACE)[0]) + module_buf += temp_buf + temp_buf = [] + module_buf.append("\n") + continue + + # If there are XML comments at the end of the file, they arn't + # attributed to anything. These are ignored. + if len(temp_buf): + warning("orphan XML comments at bottom of file %s" % file_name) + + module_buf.append("\n") + + return module_buf + +def getLayerXML(directory): + ''' + Returns the XML documentation for a layer. + ''' + + layer_buf = [] + + # Infer the layer name from the directory name. + layer_buf.append("\n" % os.path.basename(directory)) + + # Try to open the metadata file for this directory and if it exists, + # append the contents to the buffer. + try: + layer_meta = open(directory+"/"+meta, "r") + layer_buf += layer_meta.readlines() + layer_meta.close() + except: + warning("cannot open file %s for read, assuming no data"\ + % meta) + + # For each module file in the layer, add its XML. + for module in glob.glob("%s/*.if" % directory): + layer_buf += getModuleXML(module) + + layer_buf.append("\n") + + return layer_buf + +def getTunableXML(file_name): + ''' + Return all the XML for the tunables in the file specified. + ''' + + # Try to open the file, if it cant, just ignore it. + try: + tunable_file = open(file_name, "r") + tunable_code = tunable_file.readlines() + tunable_file.close() + except: + warning("cannot open file %s for read, skipping" % file_name) + return [] + + tunable_buf = [] + temp_buf = [] + + # Find tunables and booleans line by line and use the comments above + # them. + for line in tunable_code: + # If it is an XML comment, add it to the buffer and go on. + if getXMLComment(line): + temp_buf.append(getXMLComment(line)) + continue + + # Get the parameters of a TUNABLE style line. + params = getParams(line,TUNABLE) + + # If the line is not a TUNABLE style declaration, try BOOLEAN. + if not params: + params = getParams(line,BOOLEAN) + + # If the line is one of the two styles above, add a tunable tag + # and give it the data from the temprorary buffer. + if params: + tunable_buf.append\ + ("" + % (params[0], params[1])) + tunable_buf += temp_buf + temp_buf = [] + tunable_buf.append("") + + # If there are XML comments at the end of the file, they arn't + # attributed to anything. These are ignored. + if len(temp_buf): + warning("orphan XML comments at bottom of file %s" % file_name) + + return tunable_buf + +def getPolicyXML(directory): + ''' + Return the compelete reference policy XML documentation through a list, + one line per item. + ''' + + # Keep track of original path so that it will change back at the end. + old_dir = os.path.abspath(os.path.curdir) + + # Attempt to change directory into the policy directory. If it doesn't + # exist just return an empty documentation. + try: + os.chdir(directory) + except: + warning("cannot change directory to %s, ignoring"\ + % directory) + return [] + + policy_buf = [] + policy_buf.append("\n") + + # Add to the XML each layer specified by the user. + for layer in layers: + policy_buf += getLayerXML(layer) + + # Add to the XML each tunable specified by the user. + for tunable_file in tunable_files: + policy_buf += getTunableXML(tunable_file) + + + policy_buf.append("\n") + + # Return to old directory. + try: + os.chdir(old_dir) + except: + error("cannot change directory to %s" % old_dir) + + return policy_buf + +def usage(): + """ + Displays a message describing the proper usage of this script. + """ + + sys.stdout.write("usage: %s [-w] [-d directory] [-m file] "\ + % sys.argv[0]) + + sys.stdout.write("layerdirectory [layerdirectory...]\n\n") + + sys.stdout.write("Options:\n") + + sys.stdout.write("-w --warn -- "+\ + "show warnings\n") + + sys.stdout.write("-m --meta -- "+\ + "the filename of the metadata in each layer\n") + + sys.stdout.write("-d --directory -- "+\ + "directory where the layers are\n") + + sys.stdout.write("-t --tunable -- "+\ + "A file containing tunable declarations\n") + +def warning(description): + ''' + Warns the user of a non-critical error. + ''' + + if warn: + sys.stderr.write("%s: " % sys.argv[0] ) + sys.stderr.write("warning: " + description + "\n") + +def error(description): + ''' + Describes an error and exists the program. + ''' + + sys.stderr.write("%s: " % sys.argv[0] ) + sys.stderr.write("error: " + description + "\n") + sys.stderr.flush() + sys.exit(1) + + + +# MAIN PROGRAM +# Check that there are command line arguments. +if len(sys.argv) <= 1: + usage() + sys.exit(1) + + +# Parse the command line arguments +for i in range(1, len(sys.argv)): + if sys.argv[i-1] in ("-d", "--directory", "-m", "--meta",\ + "-t", "--tunable"): + continue + elif sys.argv[i] in ("-w", "--warn"): + warn = True + elif sys.argv[i] in ("-d", "--directory"): + if i < len(sys.argv)-1: + directory = sys.argv[i+1] + else: + usage() + elif sys.argv[i] in ("-m", "--meta"): + if i < len(sys.argv)-1: + meta = sys.argv[i+1] + else: + usage() + elif sys.argv[i] in ("-t", "--tunable"): + if i < len(sys.argv)-1: + tunable_files.append(sys.argv[i+1]) + else: + usage() + else: + layers.append(sys.argv[i]) + + +# Generate the XML and output it to a file +lines = getPolicyXML(directory) +for s in lines: + sys.stdout.write(s)