Blob Blame History Raw
diff --git a/src/XCCDF/benchmark.c b/src/XCCDF/benchmark.c
index f561736d4..d6560b7c9 100644
--- a/src/XCCDF/benchmark.c
+++ b/src/XCCDF/benchmark.c
@@ -866,6 +866,19 @@ xccdf_benchmark_find_target_htable(const struct xccdf_benchmark *benchmark, xccd
 	return XITEM(benchmark)->sub.benchmark.items_dict;
 }
 
+int xccdf_benchmark_include_tailored_profiles(struct xccdf_benchmark *benchmark)
+{
+	struct oscap_list *profiles = XITEM(benchmark)->sub.benchmark.profiles;
+	struct oscap_htable_iterator *it = oscap_htable_iterator_new(XITEM(benchmark)->sub.benchmark.profiles_dict);
+	while(oscap_htable_iterator_has_more(it)) {
+		struct xccdf_profile *profile = oscap_htable_iterator_next_value(it);
+		if (xccdf_profile_get_tailoring(profile)) {
+			oscap_list_add(profiles, xccdf_profile_clone(profile));
+		}
+	}
+	oscap_htable_iterator_free(it);
+	return 0;
+}
 
 struct xccdf_plain_text *xccdf_plain_text_new(void)
 {
diff --git a/src/XCCDF/item.h b/src/XCCDF/item.h
index 35204744f..da76f854d 100644
--- a/src/XCCDF/item.h
+++ b/src/XCCDF/item.h
@@ -437,6 +437,7 @@ void xccdf_item_dump(struct xccdf_item *item, int depth);
 struct xccdf_item* xccdf_item_get_benchmark_internal(struct xccdf_item* item);
 bool xccdf_benchmark_parse(struct xccdf_item *benchmark, xmlTextReaderPtr reader);
 void xccdf_benchmark_dump(struct xccdf_benchmark *benchmark);
+int xccdf_benchmark_include_tailored_profiles(struct xccdf_benchmark *benchmark);
 struct oscap_htable_iterator *xccdf_benchmark_get_cluster_items(struct xccdf_benchmark *benchmark, const char *cluster_id);
 bool xccdf_benchmark_register_item(struct xccdf_benchmark *benchmark, struct xccdf_item *item);
 bool xccdf_benchmark_unregister_item(struct xccdf_item *item);
diff --git a/src/XCCDF/public/xccdf_session.h b/src/XCCDF/public/xccdf_session.h
index ac96e2036..026432693 100644
--- a/src/XCCDF/public/xccdf_session.h
+++ b/src/XCCDF/public/xccdf_session.h
@@ -558,6 +558,14 @@ OSCAP_API int xccdf_session_build_policy_from_testresult(struct xccdf_session *s
  */
 OSCAP_API int xccdf_session_add_report_from_source(struct xccdf_session *session, struct oscap_source *report_source);
 
+/**
+ * Generate HTML guide form a loaded XCCDF session
+ * @param session XCCDF Session
+ * @param outfile path to the output file
+ * @returns zero on success
+ */
+OSCAP_API int xccdf_session_generate_guide(struct xccdf_session *session, const char *outfile);
+
 /// @}
 /// @}
 #endif
diff --git a/src/XCCDF/xccdf_session.c b/src/XCCDF/xccdf_session.c
index 1005a030a..d38905f77 100644
--- a/src/XCCDF/xccdf_session.c
+++ b/src/XCCDF/xccdf_session.c
@@ -1746,3 +1746,44 @@ int xccdf_session_add_report_from_source(struct xccdf_session *session, struct o
 	session->xccdf.result = result;
 	return 0;
 }
+
+int xccdf_session_generate_guide(struct xccdf_session *session, const char *outfile)
+{
+	struct xccdf_policy *policy = xccdf_session_get_xccdf_policy(session);
+	if (policy == NULL) {
+		oscap_seterr(OSCAP_EFAMILY_OSCAP, "Could not get XCCDF policy.");
+		return 1;
+	}
+	struct xccdf_policy_model *model = xccdf_policy_get_model(policy);
+	if (model == NULL) {
+		oscap_seterr(OSCAP_EFAMILY_OSCAP, "Could not get XCCDF policy model.");
+		return 1;
+	}
+	struct xccdf_benchmark *benchmark = xccdf_policy_model_get_benchmark(model);
+	if (benchmark == NULL) {
+		oscap_seterr(OSCAP_EFAMILY_OSCAP, "Could not get XCCDF benchmark.");
+		return 1;
+	}
+	xccdf_benchmark_include_tailored_profiles(benchmark);
+	const char *params[] = {
+		"oscap-version",     oscap_get_version(),
+		"benchmark_id",      xccdf_benchmark_get_id(benchmark),
+		"profile_id",        xccdf_session_get_profile_id(session),
+		NULL
+	};
+	struct oscap_source *benchmark_source = xccdf_benchmark_export_source(benchmark, NULL);
+	if (benchmark_source == NULL) {
+		oscap_seterr(OSCAP_EFAMILY_OSCAP,
+			"Failed to export source from XCCDF benchmark %s.",
+			xccdf_benchmark_get_id(benchmark));
+		return 1;
+	}
+	int ret = oscap_source_apply_xslt_path(benchmark_source,
+		"xccdf-guide.xsl", outfile, params, oscap_path_to_xslt());
+	oscap_source_free(benchmark_source);
+	if (ret < 0) {
+		oscap_seterr(OSCAP_EFAMILY_OSCAP, "Could not apply XSLT.");
+		return 1;
+	}
+	return 0;
+}
\ No newline at end of file
diff --git a/tests/API/XCCDF/tailoring/all.sh b/tests/API/XCCDF/tailoring/all.sh
index dbed97a87..fc2ccd743 100755
--- a/tests/API/XCCDF/tailoring/all.sh
+++ b/tests/API/XCCDF/tailoring/all.sh
@@ -134,6 +134,21 @@ function test_api_xccdf_tailoring_profile_generate_fix {
     rm -f $tailoring_result $fix_result
 }
 
+function test_api_xccdf_tailoring_profile_generate_guide {
+    local INPUT=$srcdir/$1
+    local TAILORING=$srcdir/$2
+
+    guide=`mktemp`
+    # tailoring profile only with "always fail" rule and generate HTML guide
+    $OSCAP xccdf generate guide --tailoring-file $TAILORING --profile "xccdf_com.example.www_profile_customized" --output $guide $INPUT
+
+    grep -q "Baseline Testing Profile 1 \[CUSTOMIZED\]" $guide
+    # profile 'customized' selects first rule and deselects the second
+    grep -q "xccdf_com.example.www_rule_first" $guide
+    grep -v "xccdf_com.example.www_rule_second" $guide
+    rm -f $guide
+}
+
 # Testing.
 
 test_init "test_api_xccdf_tailoring.log"
@@ -154,6 +169,7 @@ test_run "test_api_xccdf_tailoring_autonegotiation" test_api_xccdf_tailoring_aut
 test_run "test_api_xccdf_tailoring_simple_include_in_arf" test_api_xccdf_tailoring_simple_include_in_arf simple-xccdf.xml simple-tailoring.xml
 test_run "test_api_xccdf_tailoring_profile_include_in_arf" test_api_xccdf_tailoring_profile_include_in_arf baseline.xccdf.xml baseline.tailoring.xml
 test_run "test_api_xccdf_tailoring_profile_generate_fix" test_api_xccdf_tailoring_profile_generate_fix baseline.xccdf.xml baseline.tailoring.xml
+test_run "test_api_xccdf_tailoring_profile_generate_guide" test_api_xccdf_tailoring_profile_generate_guide baseline.xccdf.xml baseline.tailoring.xml
 
 
 test_exit
diff --git a/utils/oscap-xccdf.c b/utils/oscap-xccdf.c
index 472a2cc6f..87a0c7ad1 100644
--- a/utils/oscap-xccdf.c
+++ b/utils/oscap-xccdf.c
@@ -77,6 +77,7 @@ static bool getopt_xccdf(int argc, char **argv, struct oscap_action *action);
 static bool getopt_generate(int argc, char **argv, struct oscap_action *action);
 static int app_xccdf_xslt(const struct oscap_action *action);
 static int app_generate_fix(const struct oscap_action *action);
+static int app_generate_guide(const struct oscap_action *action);
 
 #define XCCDF_SUBMODULES_NUM		7
 #define XCCDF_GEN_SUBMODULES_NUM	5 /* See actual arrays
@@ -244,12 +245,18 @@ static struct oscap_module XCCDF_GEN_GUIDE = {
     .help = GEN_OPTS
 		"\nGuide Options:\n"
 		"   --output <file>               - Write the document into file.\n"
-		"   --hide-profile-info           - Do not output additional information about selected profile.\n"
+		"   --hide-profile-info           - This option has no effect.\n"
 		"   --benchmark-id <id>           - ID of XCCDF Benchmark in some component in the datastream that should be used.\n"
+		"                                   (only applicable for source datastreams)\n"
+		"   --xccdf-id <id>               - ID of component-ref with XCCDF in the datastream that should be evaluated.\n"
+		"                                   (only applicable for source datastreams)\n"
+		"   --tailoring-file <file>       - Use given XCCDF Tailoring file.\n"
+		"                                   (only applicable for source datastreams)\n"
+		"   --tailoring-id <component-id> - Use given DS component as XCCDF Tailoring file.\n"
 		"                                   (only applicable for source datastreams)\n",
     .opt_parser = getopt_xccdf,
-    .user = "xccdf-guide.xsl",
-    .func = app_xccdf_xslt
+    .user = NULL,
+    .func = app_generate_guide
 };
 
 static struct oscap_module XCCDF_GEN_FIX = {
@@ -973,6 +980,50 @@ int app_generate_fix(const struct oscap_action *action)
 	return ret;
 }
 
+int app_generate_guide(const struct oscap_action *action)
+{
+	int ret = OSCAP_ERROR;
+
+	struct oscap_source *source = oscap_source_new_from_file(action->f_xccdf);
+	struct xccdf_session *session = xccdf_session_new_from_source(source);
+	if (session == NULL) {
+		goto cleanup;
+	}
+
+	xccdf_session_set_validation(session, action->validate, getenv("OSCAP_FULL_VALIDATION") != NULL);
+	xccdf_session_set_remote_resources(session, action->remote_resources, download_reporting_callback);
+	xccdf_session_set_user_tailoring_file(session, action->tailoring_file);
+	xccdf_session_set_user_tailoring_cid(session, action->tailoring_id);
+	if (xccdf_session_is_sds(session)) {
+		xccdf_session_set_component_id(session, action->f_xccdf_id);
+		xccdf_session_set_benchmark_id(session, action->f_benchmark_id);
+	}
+	xccdf_session_set_loading_flags(session, XCCDF_SESSION_LOAD_XCCDF);
+
+	if (xccdf_session_load(session) != 0) {
+		goto cleanup;
+	}
+
+	if (!xccdf_session_set_profile_id(session, action->profile)) {
+		if (action->profile != NULL) {
+			if (xccdf_set_profile_or_report_bad_id(session, action->profile, action->f_xccdf) == OSCAP_ERROR)
+				goto cleanup;
+		} else {
+			fprintf(stderr, "No Policy was found for default profile.\n");
+			goto cleanup;
+		}
+	}
+
+	if (xccdf_session_generate_guide(session, action->f_results) == 0) {
+		ret = OSCAP_OK;
+	}
+
+cleanup:
+	xccdf_session_free(session);
+	oscap_print_error();
+	return ret;
+}
+
 int app_xccdf_xslt(const struct oscap_action *action)
 {
 	const char *oval_template = action->oval_template;
diff --git a/utils/oscap.8 b/utils/oscap.8
index 1b05f8c9e..3a50d1df5 100644
--- a/utils/oscap.8
+++ b/utils/oscap.8
@@ -331,10 +331,19 @@ Generate a HTML document containing a security guide from an XCCDF Benchmark. Un
 Write the guide to this file instead of standard output.
 .TP
 \fB\-\-hide-profile-info\fR
-Information on chosen profile (e.g. rules selected by the profile) will be excluded from the document.
+This option has no effect and is kept only for backward compatibility purposes.
 .TP
 \fB\-\-benchmark-id ID\fR
 Selects a component ref from any datastream that references a component with XCCDF Benchmark such that its @id attribute matches given string exactly.
+.TP
+\fB\-\-xccdf-id ID\fR
+Takes component ref with given ID from checklists. This allows to select a particular XCCDF component even in cases where there are 2 XCCDFs in one datastream. If none is given, the first component from the checklists element is used.
+.TP
+\fB\-\-tailoring-file TAILORING_FILE\fR
+Use given file for XCCDF tailoring. Select profile from tailoring file to apply using --profile. If both --tailoring-file and --tailoring-id are specified, --tailoring-file takes priority.
+.TP
+\fB\-\-tailoring-id COMPONENT_REF_ID\fR
+Use tailoring component in input source datastream for XCCDF tailoring. The tailoring component must be specified by its Ref-ID (value of component-ref/@id attribute in input source datastream). Select profile from tailoring component to apply using --profile. If both --tailoring-file and --tailoring-id are specified, --tailoring-file takes priority.
 .RE
 .TP
 .B \fBreport\fR  [\fIoptions\fR] xccdf-file