Chris PeBenito 17de1b
#!/usr/bin/python
Chris PeBenito 17de1b
Chris PeBenito 17de1b
#  Author(s): Donald Miner <dminer@tresys.com>
Chris PeBenito 17de1b
#             Dave Sugar <dsugar@tresys.com>
Chris PeBenito 17de1b
#             Brian Williams <bwilliams@tresys.com>
Chris PeBenito 56e1b3
#             Caleb Case <ccase@tresys.com>
Chris PeBenito 17de1b
#
Chris PeBenito 17de1b
# Copyright (C) 2005 - 2006 Tresys Technology, LLC
Chris PeBenito 17de1b
#      This program is free software; you can redistribute it and/or modify
Chris PeBenito 17de1b
#      it under the terms of the GNU General Public License as published by
Chris PeBenito 17de1b
#      the Free Software Foundation, version 2.
Chris PeBenito 17de1b
Chris PeBenito 17de1b
"""
Chris PeBenito 17de1b
	This script generates XML documentation information for layers specified
Chris PeBenito 17de1b
	by the user.
Chris PeBenito 17de1b
"""
Chris PeBenito 17de1b
Chris PeBenito 17de1b
import sys
Chris PeBenito 17de1b
import os
Chris PeBenito 17de1b
import glob
Chris PeBenito 17de1b
import re
Chris PeBenito 56e1b3
import getopt
Chris PeBenito 17de1b
Chris PeBenito 17de1b
# GLOBALS
Chris PeBenito 17de1b
Chris PeBenito 17de1b
# Default values of command line arguments:
Chris PeBenito 17de1b
warn = False
Chris PeBenito 17de1b
meta = "metadata"
Chris PeBenito 17de1b
third_party = "third-party"
Chris PeBenito 17de1b
layers = {}
Chris PeBenito 17de1b
tunable_files = []
Chris PeBenito 17de1b
bool_files = []
Chris PeBenito 17de1b
xml_tunable_files = []
Chris PeBenito 17de1b
xml_bool_files = []
Chris PeBenito 17de1b
output_dir = ""
Chris PeBenito 17de1b
Chris PeBenito 17de1b
# Pre compiled regular expressions:
Chris PeBenito 17de1b
Chris PeBenito 17de1b
# Matches either an interface or a template declaration. Will give the tuple:
Chris PeBenito 17de1b
#	("interface" or "template", name)
Chris PeBenito 17de1b
# Some examples:
Chris PeBenito 17de1b
#	"interface(`kernel_read_system_state',`"
Chris PeBenito 17de1b
#	 -> ("interface", "kernel_read_system_state")
Chris PeBenito 17de1b
#	"template(`base_user_template',`"
Chris PeBenito 17de1b
#	 -> ("template", "base_user_template")
Chris PeBenito 17de1b
INTERFACE = re.compile("^\s*(interface|template)\(`(\w*)'")
Chris PeBenito 17de1b
Chris PeBenito 17de1b
# Matches either a gen_bool or a gen_tunable statement. Will give the tuple:
Chris PeBenito 17de1b
#	("tunable" or "bool", name, "true" or "false")
Chris PeBenito 17de1b
# Some examples:
Chris PeBenito 17de1b
#	"gen_bool(secure_mode, false)"
Chris PeBenito 17de1b
#	 -> ("bool", "secure_mode", "false")
Chris PeBenito 17de1b
#	"gen_tunable(allow_kerberos, false)"
Chris PeBenito 17de1b
#	 -> ("tunable", "allow_kerberos", "false")
Chris PeBenito 17de1b
BOOLEAN = re.compile("^\s*gen_(tunable|bool)\(\s*(\w*)\s*,\s*(true|false)\s*\)")
Chris PeBenito 17de1b
Chris PeBenito 17de1b
# Matches a XML comment in the policy, which is defined as any line starting
Chris PeBenito 17de1b
#  with two # and at least one character of white space. Will give the single
Chris PeBenito 17de1b
#  valued tuple:
Chris PeBenito 17de1b
#	("comment")
Chris PeBenito 17de1b
# Some Examples:
Chris PeBenito 17de1b
#	"## <summary>"
Chris PeBenito 17de1b
#	 -> ("<summary>")
Chris PeBenito 17de1b
#	"##		The domain allowed access.	"
Chris PeBenito 17de1b
#	 -> ("The domain allowed access.")
Chris PeBenito 17de1b
XML_COMMENT = re.compile("^##\s+(.*?)\s*$")
Chris PeBenito 17de1b
Chris PeBenito 17de1b
Chris PeBenito 17de1b
# FUNCTIONS
Chris PeBenito 17de1b
def getModuleXML(file_name):
Chris PeBenito 17de1b
	'''
Chris PeBenito 17de1b
	Returns the XML data for a module in a list, one line per list item.
Chris PeBenito 17de1b
	'''
Chris PeBenito 17de1b
Chris PeBenito 56e1b3
	# Gather information.
Chris PeBenito 56e1b3
	module_dir = os.path.dirname(file_name)
Chris PeBenito 56e1b3
	module_name = os.path.basename(file_name)
Chris PeBenito 56e1b3
	module_te = "%s/%s.te" % (module_dir, module_name)
Chris PeBenito 56e1b3
	module_if = "%s/%s.if" % (module_dir, module_name)
Chris PeBenito 56e1b3
Chris PeBenito 17de1b
	# Try to open the file, if it cant, just ignore it.
Chris PeBenito 17de1b
	try:
Chris PeBenito 56e1b3
		module_file = open(module_if, "r")
Chris PeBenito 17de1b
		module_code = module_file.readlines()
Chris PeBenito 17de1b
		module_file.close()
Chris PeBenito 17de1b
	except:
Chris PeBenito 17de1b
		warning("cannot open file %s for read, skipping" % file_name)
Chris PeBenito 17de1b
		return []
Chris PeBenito 17de1b
Chris PeBenito 17de1b
	module_buf = []
Chris PeBenito 17de1b
Chris PeBenito 17de1b
	# Infer the module name, which is the base of the file name.
Chris PeBenito 17de1b
	module_buf.append("<module name=\"%s\" filename=\"%s\">\n" 
Chris PeBenito 56e1b3
		% (os.path.splitext(os.path.split(file_name)[-1])[0], module_if))
Chris PeBenito 17de1b
Chris PeBenito 17de1b
	temp_buf = []
Chris PeBenito 17de1b
	interface = None
Chris PeBenito 17de1b
Chris PeBenito 17de1b
	# finding_header is a flag to denote whether we are still looking
Chris PeBenito 17de1b
	#  for the XML documentation at the head of the file.
Chris PeBenito 17de1b
	finding_header = True
Chris PeBenito 17de1b
Chris PeBenito 17de1b
	# Get rid of whitespace at top of file
Chris PeBenito 17de1b
	while(module_code and module_code[0].isspace()):
Chris PeBenito 17de1b
		module_code = module_code[1:]
Chris PeBenito 17de1b
Chris PeBenito 17de1b
	# Go line by line and figure out what to do with it.
Chris PeBenito 17de1b
	line_num = 0
Chris PeBenito 17de1b
	for line in module_code:
Chris PeBenito 17de1b
		line_num += 1
Chris PeBenito 17de1b
		if finding_header:
Chris PeBenito 17de1b
			# If there is a XML comment, add it to the temp buffer.
Chris PeBenito 17de1b
			comment = XML_COMMENT.match(line)
Chris PeBenito 17de1b
			if comment:
Chris PeBenito 17de1b
				temp_buf.append(comment.group(1) + "\n")
Chris PeBenito 17de1b
				continue
Chris PeBenito 17de1b
Chris PeBenito 17de1b
			# Once a line that is not an XML comment is reached,
Chris PeBenito 17de1b
			#  either put the XML out to module buffer as the
Chris PeBenito 17de1b
			#  module's documentation, or attribute it to an
Chris PeBenito 17de1b
			#  interface/template.
Chris PeBenito 17de1b
			elif temp_buf:
Chris PeBenito 17de1b
				finding_header = False
Chris PeBenito 17de1b
				interface = INTERFACE.match(line)
Chris PeBenito 17de1b
				if not interface:
Chris PeBenito 17de1b
					module_buf += temp_buf
Chris PeBenito 17de1b
					temp_buf = []
Chris PeBenito 17de1b
					continue
Chris PeBenito 17de1b
Chris PeBenito 17de1b
		# Skip over empty lines
Chris PeBenito 17de1b
		if line.isspace():
Chris PeBenito 17de1b
			continue
Chris PeBenito 17de1b
Chris PeBenito 17de1b
		# Grab a comment and add it to the temprorary buffer, if it
Chris PeBenito 17de1b
		#  is there.
Chris PeBenito 17de1b
		comment = XML_COMMENT.match(line)
Chris PeBenito 17de1b
		if comment:
Chris PeBenito 17de1b
			temp_buf.append(comment.group(1) + "\n")
Chris PeBenito 17de1b
			continue
Chris PeBenito 17de1b
Chris PeBenito 17de1b
		# Grab the interface information. This is only not true when
Chris PeBenito 17de1b
		#  the interface is at the top of the file and there is no
Chris PeBenito 17de1b
		#  documentation for the module.
Chris PeBenito 17de1b
		if not interface:
Chris PeBenito 17de1b
			interface = INTERFACE.match(line)
Chris PeBenito 17de1b
		if interface:
Chris PeBenito 17de1b
			# Add the opening tag for the interface/template
Chris PeBenito 17de1b
			groups = interface.groups()
Chris PeBenito 17de1b
			module_buf.append("<%s name=\"%s\" lineno=\"%s\">\n" % (groups[0], groups[1], line_num))
Chris PeBenito 17de1b
Chris PeBenito 17de1b
			# Add all the comments attributed to this interface to
Chris PeBenito 17de1b
			#  the module buffer.
Chris PeBenito 17de1b
			if temp_buf:
Chris PeBenito 17de1b
				module_buf += temp_buf
Chris PeBenito 17de1b
				temp_buf = []
Chris PeBenito 17de1b
Chris PeBenito 17de1b
			# Add default summaries and parameters so that the
Chris PeBenito 17de1b
			#  DTD is happy.
Chris PeBenito 17de1b
			else:
Chris PeBenito 17de1b
				warning ("unable to find XML for %s %s()" % (groups[0], groups[1]))	
Chris PeBenito 17de1b
				module_buf.append("<summary>\n")
Chris PeBenito 17de1b
				module_buf.append("Summary is missing!\n")
Chris PeBenito 17de1b
				module_buf.append("</summary>\n")
Chris PeBenito 17de1b
				module_buf.append("<param name=\"?\">\n")
Chris PeBenito 17de1b
				module_buf.append("<summary>\n")
Chris PeBenito 17de1b
				module_buf.append("Parameter descriptions are missing!\n")
Chris PeBenito 17de1b
				module_buf.append("</summary>\n")
Chris PeBenito 17de1b
				module_buf.append("</param>\n")
Chris PeBenito 17de1b
Chris PeBenito 17de1b
			# Close the interface/template tag.
Chris PeBenito 17de1b
			module_buf.append("</%s>\n" % interface.group(1))
Chris PeBenito 17de1b
Chris PeBenito 17de1b
			interface = None
Chris PeBenito 17de1b
			continue
Chris PeBenito 17de1b
Chris PeBenito 17de1b
Chris PeBenito 17de1b
Chris PeBenito 17de1b
	# If the file just had a header, add the comments to the module buffer.
Chris PeBenito 17de1b
	if finding_header:
Chris PeBenito 17de1b
		module_buf += temp_buf
Chris PeBenito 17de1b
	# Otherwise there are some lingering XML comments at the bottom, warn
Chris PeBenito 17de1b
	#  the user.
Chris PeBenito 17de1b
	elif temp_buf:
Chris PeBenito 17de1b
		warning("orphan XML comments at bottom of file %s" % file_name)
Chris PeBenito 17de1b
Chris PeBenito 56e1b3
	# Process the TE file if it exists.
Chris PeBenito 56e1b3
	module_buf = module_buf + getTunableXML(module_te, "both")
Chris PeBenito 56e1b3
Chris PeBenito 17de1b
	module_buf.append("</module>\n")
Chris PeBenito 17de1b
Chris PeBenito 17de1b
	return module_buf
Chris PeBenito 17de1b
Chris PeBenito 17de1b
def getTunableXML(file_name, kind):
Chris PeBenito 17de1b
	'''
Chris PeBenito 17de1b
	Return all the XML for the tunables/bools in the file specified.
Chris PeBenito 17de1b
	'''
Chris PeBenito 17de1b
Chris PeBenito 17de1b
	# Try to open the file, if it cant, just ignore it.
Chris PeBenito 17de1b
	try:
Chris PeBenito 17de1b
		tunable_file = open(file_name, "r")
Chris PeBenito 17de1b
		tunable_code = tunable_file.readlines()
Chris PeBenito 17de1b
		tunable_file.close()
Chris PeBenito 17de1b
	except:
Chris PeBenito 17de1b
		warning("cannot open file %s for read, skipping" % file_name)
Chris PeBenito 17de1b
		return []
Chris PeBenito 17de1b
Chris PeBenito 17de1b
	tunable_buf = []
Chris PeBenito 17de1b
	temp_buf = []
Chris PeBenito 17de1b
Chris PeBenito 17de1b
	# Find tunables and booleans line by line and use the comments above
Chris PeBenito 17de1b
	# them.
Chris PeBenito 17de1b
	for line in tunable_code:
Chris PeBenito 17de1b
		# If it is an XML comment, add it to the buffer and go on.
Chris PeBenito 17de1b
		comment = XML_COMMENT.match(line)
Chris PeBenito 17de1b
		if comment:
Chris PeBenito 17de1b
			temp_buf.append(comment.group(1) + "\n")
Chris PeBenito 17de1b
			continue
Chris PeBenito 17de1b
Chris PeBenito 17de1b
		# Get the boolean/tunable data.
Chris PeBenito 17de1b
		boolean = BOOLEAN.match(line)
Chris PeBenito 17de1b
Chris PeBenito 17de1b
		# If we reach a boolean/tunable declaration, attribute all XML
Chris PeBenito 17de1b
		#  in the temp buffer to it and add XML to the tunable buffer.
Chris PeBenito 17de1b
		if boolean:
Chris PeBenito 17de1b
			# If there is a gen_bool in a tunable file or a
Chris PeBenito 17de1b
			# gen_tunable in a boolean file, error and exit.
Chris PeBenito 56e1b3
			# Skip if both kinds are valid.
Chris PeBenito 56e1b3
			if kind != "both":
Chris PeBenito 56e1b3
				if boolean.group(1) != kind:
Chris PeBenito 56e1b3
					error("%s in a %s file." % (boolean.group(1), kind))
Chris PeBenito 17de1b
Chris PeBenito 17de1b
			tunable_buf.append("<%s name=\"%s\" dftval=\"%s\">\n" % boolean.groups())
Chris PeBenito 17de1b
			tunable_buf += temp_buf
Chris PeBenito 17de1b
			temp_buf = []
Chris PeBenito 17de1b
			tunable_buf.append("</%s>\n" % boolean.group(1))
Chris PeBenito 17de1b
Chris PeBenito 17de1b
	# If there are XML comments at the end of the file, they arn't
Chris PeBenito 17de1b
	# attributed to anything. These are ignored.
Chris PeBenito 17de1b
	if len(temp_buf):
Chris PeBenito 17de1b
		warning("orphan XML comments at bottom of file %s" % file_name)
Chris PeBenito 17de1b
Chris PeBenito 17de1b
Chris PeBenito 17de1b
	# If the caller requested a the global_tunables and global_booleans to be
Chris PeBenito 17de1b
	# output to a file output them now
Chris PeBenito 17de1b
	if len(output_dir) > 0:
Chris PeBenito 17de1b
		xmlfile = os.path.split(file_name)[1] + ".xml"
Chris PeBenito 17de1b
Chris PeBenito 17de1b
		try:
Chris PeBenito 17de1b
			xml_outfile = open(output_dir + "/" + xmlfile, "w")
Chris PeBenito 17de1b
			for tunable_line in tunable_buf:
Chris PeBenito 17de1b
				xml_outfile.write (tunable_line)
Chris PeBenito 17de1b
			xml_outfile.close()
Chris PeBenito 17de1b
		except:
Chris PeBenito 17de1b
			warning ("cannot write to file %s, skipping creation" % xmlfile)
Chris PeBenito 17de1b
Chris PeBenito 17de1b
	return tunable_buf
Chris PeBenito 17de1b
Chris PeBenito 17de1b
def getXMLFileContents (file_name):
Chris PeBenito 17de1b
	'''
Chris PeBenito 17de1b
	Return all the XML in the file specified.
Chris PeBenito 17de1b
	'''
Chris PeBenito 17de1b
Chris PeBenito 17de1b
	tunable_buf = []
Chris PeBenito 17de1b
	# Try to open the xml file for this type of file
Chris PeBenito 17de1b
	# append the contents to the buffer.
Chris PeBenito 17de1b
	try:
Chris PeBenito 17de1b
		tunable_xml = open(file_name, "r")
Chris PeBenito 17de1b
		tunable_buf += tunable_xml.readlines()
Chris PeBenito 17de1b
		tunable_xml.close()
Chris PeBenito 17de1b
	except:
Chris PeBenito 17de1b
		warning("cannot open file %s for read, assuming no data" % file_name)
Chris PeBenito 17de1b
Chris PeBenito 17de1b
	return tunable_buf
Chris PeBenito 17de1b
Chris PeBenito 17de1b
def getPolicyXML():
Chris PeBenito 17de1b
	'''
Chris PeBenito 17de1b
	Return the compelete reference policy XML documentation through a list,
Chris PeBenito 17de1b
	one line per item.
Chris PeBenito 17de1b
	'''
Chris PeBenito 17de1b
Chris PeBenito 17de1b
	policy_buf = []
Chris PeBenito 17de1b
	policy_buf.append("<policy>\n")
Chris PeBenito 17de1b
Chris PeBenito 17de1b
	# Add to the XML each layer specified by the user.
Chris PeBenito 17de1b
	for layer in layers.keys ():
Chris PeBenito 17de1b
		policy_buf += getLayerXML(layer, layers[layer])
Chris PeBenito 17de1b
Chris PeBenito 17de1b
	# Add to the XML each tunable file specified by the user.
Chris PeBenito 17de1b
	for tunable_file in tunable_files:
Chris PeBenito 17de1b
		policy_buf += getTunableXML(tunable_file, "tunable")
Chris PeBenito 17de1b
Chris PeBenito 17de1b
	# Add to the XML each XML tunable file specified by the user.
Chris PeBenito 17de1b
	for tunable_file in xml_tunable_files:
Chris PeBenito 17de1b
		policy_buf += getXMLFileContents (tunable_file)
Chris PeBenito 17de1b
Chris PeBenito 17de1b
	# Add to the XML each bool file specified by the user.
Chris PeBenito 17de1b
	for bool_file in bool_files:
Chris PeBenito 17de1b
		policy_buf += getTunableXML(bool_file, "bool")
Chris PeBenito 17de1b
Chris PeBenito 17de1b
	# Add to the XML each XML bool file specified by the user.
Chris PeBenito 17de1b
	for bool_file in xml_bool_files:
Chris PeBenito 17de1b
		policy_buf += getXMLFileContents (bool_file)
Chris PeBenito 17de1b
Chris PeBenito 17de1b
	policy_buf.append("</policy>\n")
Chris PeBenito 17de1b
Chris PeBenito 17de1b
	return policy_buf
Chris PeBenito 17de1b
Chris PeBenito 17de1b
def usage():
Chris PeBenito 17de1b
	"""
Chris PeBenito 17de1b
	Displays a message describing the proper usage of this script.
Chris PeBenito 17de1b
	"""
Chris PeBenito 17de1b
Chris PeBenito 56e1b3
	sys.stdout.write("usage: %s [-w] [-mtb] <file>\n\n" % sys.argv[0])
Chris PeBenito 56e1b3
	sys.stdout.write("-w --warn\t\t\tshow warnings\n"+\
Chris PeBenito 56e1b3
	"-m --module <file>\t\tname of module to process\n"+\
Chris PeBenito 56e1b3
	"-t --tunable <file>\t\tname of global tunable file to process\n"+\
Chris PeBenito 56e1b3
	"-b --boolean <file>\t\tname of global boolean file to process\n\n")
Chris PeBenito 17de1b
Chris PeBenito 56e1b3
	sys.stdout.write("examples:\n")
Chris PeBenito 56e1b3
	sys.stdout.write("> %s -w -m policy/modules/apache\n" % sys.argv[0])
Chris PeBenito 56e1b3
	sys.stdout.write("> %s -t policy/global_tunables\n" % sys.argv[0])
Chris PeBenito 17de1b
Chris PeBenito 17de1b
def warning(description):
Chris PeBenito 17de1b
	'''
Chris PeBenito 17de1b
	Warns the user of a non-critical error.
Chris PeBenito 17de1b
	'''
Chris PeBenito 17de1b
Chris PeBenito 17de1b
	if warn:
Chris PeBenito 17de1b
		sys.stderr.write("%s: " % sys.argv[0] )
Chris PeBenito 17de1b
		sys.stderr.write("warning: " + description + "\n")
Chris PeBenito 17de1b
Chris PeBenito 17de1b
def error(description):
Chris PeBenito 17de1b
	'''
Chris PeBenito 17de1b
	Describes an error and exists the program.
Chris PeBenito 17de1b
	'''
Chris PeBenito 17de1b
Chris PeBenito 17de1b
	sys.stderr.write("%s: " % sys.argv[0] )
Chris PeBenito 17de1b
        sys.stderr.write("error: " + description + "\n")
Chris PeBenito 17de1b
        sys.stderr.flush()
Chris PeBenito 17de1b
        sys.exit(1)
Chris PeBenito 17de1b
Chris PeBenito 17de1b
Chris PeBenito 17de1b
Chris PeBenito 17de1b
# MAIN PROGRAM
Chris PeBenito 56e1b3
Chris PeBenito 56e1b3
# Defaults
Chris PeBenito 56e1b3
warn = False
Chris PeBenito 56e1b3
module = False
Chris PeBenito 56e1b3
tunable = False
Chris PeBenito 56e1b3
boolean = False
Chris PeBenito 56e1b3
Chris PeBenito 17de1b
# Check that there are command line arguments.
Chris PeBenito 17de1b
if len(sys.argv) <= 1:
Chris PeBenito 17de1b
	usage()
Chris PeBenito 17de1b
	sys.exit(1)
Chris PeBenito 17de1b
Chris PeBenito 56e1b3
# Parse command line args
Chris PeBenito 56e1b3
try:
Chris PeBenito 56e1b3
	opts, args = getopt.getopt(sys.argv[1:], 'whm:t:b:', ['warn', 'help', 'module=', 'tunable=', 'boolean='])
Chris PeBenito 56e1b3
except getopt.GetoptError:
Chris PeBenito 56e1b3
	usage()
Chris PeBenito 56e1b3
	sys.exit(2)
Chris PeBenito 56e1b3
for o, a in opts:
Chris PeBenito 56e1b3
	if o in ('-w', '--warn'):
Chris PeBenito 17de1b
		warn = True
Chris PeBenito 56e1b3
	elif o in ('-h', '--help'):
Chris PeBenito 56e1b3
		usage()
Chris PeBenito 56e1b3
		sys.exit(0)
Chris PeBenito 56e1b3
	elif o in ('-m', '--module'):
Chris PeBenito 56e1b3
		module = a
Chris PeBenito 56e1b3
		break
Chris PeBenito 56e1b3
	elif o in ('-t', '--tunable'):
Chris PeBenito 56e1b3
		tunable = a
Chris PeBenito 56e1b3
		break
Chris PeBenito 56e1b3
	elif o in ('-b', '--boolean'):
Chris PeBenito 56e1b3
		boolean = a
Chris PeBenito 56e1b3
		break
Chris PeBenito 17de1b
	else:
Chris PeBenito 56e1b3
		usage()
Chris PeBenito 56e1b3
		sys.exit(2)
Chris PeBenito 56e1b3
Chris PeBenito 56e1b3
if module:
Chris PeBenito 56e1b3
	sys.stdout.writelines(getModuleXML(module))
Chris PeBenito 56e1b3
elif tunable:
Chris PeBenito 56e1b3
	sys.stdout.writelines(getTunableXML(tunable, "tunable"))
Chris PeBenito 56e1b3
elif boolean:
Chris PeBenito 56e1b3
	sys.stdout.writelines(getTunableXML(boolean, "bool"))
Chris PeBenito 56e1b3
else:
Chris PeBenito 56e1b3
	usage()
Chris PeBenito 56e1b3
	sys.exit(2)
Chris PeBenito 17de1b