#!/bin/bash
# vim: dict=/usr/share/beakerlib/dictionary.vim cpt=.,w,b,u,t,i,k
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# lib.sh of /CoreOS/selinux-policy/Library/common
# Description: Common library for SELinux related components
# Authors: Milos Malik <mmalik@redhat.com>
# Michal Trunecka <mtruneck@redhat.com>
# David Spurek <dspurek@redhat.com>
# Jiri Jaburek <jjaburek@redhat.com>
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# Copyright (c) 2020 Red Hat, Inc. All rights reserved.
#
# This copyrighted material is made available to anyone wishing
# to use, modify, copy, or redistribute it subject to the terms
# and conditions of the GNU General Public License version 2.
#
# 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, write to the Free
# Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
# Boston, MA 02110-1301, USA.
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# library-prefix = rlSE
# library-version = 42
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<'=cut'
=pod
=head1 NAME
selinux-policy/common - BeakerLib extension for managing SELinux
=cut
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Variables
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<'=cut'
=pod
=head1 VARIABLES
=over
=item rlSE_CACHE_DIR
Used by sesearch caching for storing the ~90M cache file. Defaults to
a subdirectory under /var/tmp.
=back
=cut
rlSE_CACHE_DIR="${rlSE_CACHE_DIR:-$__INTERNAL_PERSISTENT_TMP/BEAKERLIB-rlSE}"
__INTERNAL_rlSE_CACHEFILE="$rlSE_CACHE_DIR/cache.db"
__INTERNAL_rlSE_SUMFILE="$rlSE_CACHE_DIR/cache.policy-checksum"
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Functions
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
true <<'=cut'
=pod
=head1 FUNCTIONS
=head2 rlSESearchRule
Searches for given SELinux rule. Checks if the rule is in the boolean if given. Checks if the rule is in the current policy, if not given other (e.g. mls).
Usage:
rlSESearchRule "RULE" [EXP_RESULT] [DESC]
rlSESearchRule "RULE [[BOOLEAN]] [POLICY]" [EXP_RESULT] [DESC]
=over
=item EXP_RESULT
Normally expected is '0' but you can the negation using '1'.
=item DESC
=back
Examples:
rlSESearchRule "dontaudit smbd_t etc_conf_t:dir { getattr open }"
rlSESearchRule "dontaudit smbd_t etc_conf_t:dir { getattr open } []"
rlSESearchRule "allow ftpd_t public_content_rw_t: dir write [ allow_ftpd_anon_write ]"
rlSESearchRule "allow ftpd_t public_content_rw_t: dir write [ allow_ftpd_anon_write ] mls"
rlSESearchRule "typeattribute httpd_sys_script_exec_t file_type, exec_type"
rlSESearchRule "type_transition initrc_t kdumpgui_exec_t : process kdumpgui_t"
rlSESearchRule "type_transition authconfig_t etc_t : file bootloader_etc_t zipl.conf"
rlSESearchRule "type_transition authconfig_t etc_t : file bootloader_etc_t [] zipl.conf mls"
=cut
rlSELogVar() {
[[ -n "$DEBUG" ]] && {
echo -n 'eval '
while [[ -n "$1" ]]; do
echo -n "rlLogDebug \"\$FUNCNAME(): \$(set | grep -P '^$1=')\";"
shift
done
}
}
rlSESearchRule() {
if [ -e /var/run/nosesearch ]; then
echo "rlSESearchRule disabled by /var/run/nosesearch"
return 0
fi
local LF="
"
local result=0
local RULE="$(echo "$1" | tr ',:&;' ' ' | tr -s ' ')"
local expected_result="${2:-0}"
local comment="$3"
# TODO: takes ~1 second, abstract to a separate function that remembers
# the version in some __INTERNAL_rlSE_* variable
local SESEARCH_OPTS=''
if [[ ${SETOOLS_MAJOR_VERSION} == "3" ]] ; then
SESEARCH_OPTS='-C'
fi
rlIsRHEL '<6' && SESEARCH_OPTS="-i -n $SESEARCH_OPTS"
rlLogInfo "$FUNCNAME: ${comment:-"checking rule '$1'"}"
`rlSELogVar 'RULE'`
# get policy type
local POLICY="$__INTERNAL_POLICY_NAME"
local POLICY_PATH=''
[[ "$RULE" =~ $(echo '(.*)\s+(mls|targeted|minimum|strict)') ]] && {
rlLogDebug "$FUNCNAME: POLICY PARSE"; `rlSELogVar 'BASH_REMATCH'`
POLICY="${BASH_REMATCH[2]}"
RULE="${BASH_REMATCH[1]}"
`rlSELogVar 'RULE'`
POLICY_PATH="$__INTERNAL_POLICY_ROOT/$POLICY/policy/policy.$(ls -1 -d $__INTERNAL_POLICY_ROOT/$POLICY/policy/policy.* | sed -r 's/[^.]*\.//' | sort -nr | head -n 1)"
}
`rlSELogVar 'POLICY' 'POLICY_PATH' RULE`
# check typeattribute rule
# - typeattribute TYPE ATTR ...
[[ "$RULE" =~ $(echo 'typeattribute\s+(\S+)\s+(\S+.*)') ]] && {
rlLogDebug "$FUNCNAME(): typeattribute PARSE"; `rlSELogVar 'BASH_REMATCH'`
local TYPE="${BASH_REMATCH[1]}"
local ATTRIBUTE=( ${BASH_REMATCH[2]} )
`rlSELogVar 'TYPE' 'ATTRIBUTE'`
local seinfo_out
while [[ -n "$ATTRIBUTE" ]]; do
rlLogDebug "$FUNCNAME(): EXECUTING seinfo_out=\"\$(seinfo -xa$ATTRIBUTE $POLICY_PATH)\""
seinfo_out="$(seinfo -xa$ATTRIBUTE $POLICY_PATH)"
if [[ $? -ne 0 ]] ; then
rlLogError "$FUNCNAME: seinfo failed"
rlLogError "$FUNCNAME: $seinfo_out"
let result++
fi
rlLogDebug "$FUNCNAME(): EXECUTING echo \"\$seinfo_out\" | grep -q \"\<${TYPE}\>\""
echo "$seinfo_out" | grep -q "\<${TYPE}\>"
rlAssertEquals "${comment:-"check if type '$TYPE' is present in attribute '$ATTRIBUTE' in policy '$POLICY'"}" $? $expected_result || let result++
ATTRIBUTE=( "${ATTRIBUTE[@]:1}" )
done
return $result
}
# check other rules (allow, dontaudit, type_transition)
# look for conditional boolean
local BOOLEAN='.*'
[[ "$RULE" =~ $(echo '(.*)(\s+\[(.*)\])(.*)') ]] && {
rlLogDebug "$FUNCNAME(): BOOLEAN PARSE"; `rlSELogVar 'BASH_REMATCH'`
BOOLEAN=( ${BASH_REMATCH[3]} )
RULE="${BASH_REMATCH[1]}${BASH_REMATCH[4]}"
}
`rlSELogVar 'BOOLEAN' RULE`
# actually check the rules
# T/F RULETYPE SCONTEXT TCONTEXT CLASS <PERM|{ PREMs }>
[[ "$RULE" =~ $(echo '((T|F)\s+)?(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.*)') ]] && {
rlLogDebug "$FUNCNAME(): RULE PARSE"; `rlSELogVar 'BASH_REMATCH'`
local TF="${BASH_REMATCH[2]}"
local RULETYPE="${BASH_REMATCH[3]}"
local SCONTEXT="${BASH_REMATCH[4]}"
local TCONTEXT="${BASH_REMATCH[5]}"
local CLASS="${BASH_REMATCH[6]}"
local PERM="${BASH_REMATCH[7]}"
local TRANS_FILE=''
if rlIsRHEL '<6'; then
rlLogDebug "$FUNCNAME(): RULE PARSE"; `rlSELogVar 'BASH_REMATCH'`
rlLogDebug "$FUNCNAME(): excluding permission 'open'"
# for RHEL < 6 ignore open permission as it isnot defined there
local PERM=( $(echo "${PERM}" | tr -d '{}' | sed -r 's/\<open\>//g') )
else
local PERM=( $(echo "${PERM}" | tr -d '{}') )
fi
local SEARCH_RULETYPE="$RULETYPE"
[[ "$RULETYPE" == "dontaudit" ]] && rlIsRHEL '<6' && SEARCH_RULETYPE="audit" # dontaudit rules are in --audit rule type
if [[ "$RULETYPE" == "type_transition" ]]; then
if [[ ${SETOOLS_MAJOR_VERSION} == "3" ]]; then
SEARCH_RULETYPE="type" # type_transition rules are in --type rule type
else # setools v.4
SEARCH_RULETYPE="type_trans" # type_transition rules are in --type_trans rule type
fi
if [[ -n "${PERM[1]}" ]] ; then
`rlSELogVar 'PERM'`
TRANS_FILE="${PERM[1]}"
unset PERM[1]
fi
fi
# sesearch v.3 --type covers type_trans, type_member, type_change
# sesearch v.4 recognizes 3 different options for ^^^
if [[ "$RULETYPE" == "type_change" ]]; then
if [[ ${SETOOLS_MAJOR_VERSION} == "3" ]]; then
SEARCH_RULETYPE="type"
else # setools v.4
SEARCH_RULETYPE="type_change"
fi
if [[ -n "${PERM[1]}" ]] ; then
`rlSELogVar 'PERM'`
TRANS_FILE="${PERM[1]}"
unset PERM[1]
fi
fi
# replace 'self' target context by source context
[[ "$TCONTEXT" == "self" ]] && TCONTEXT="$SCONTEXT"
`rlSELogVar 'TF' 'RULETYPE' 'SCONTEXT' 'TCONTEXT' 'CLASS' 'PERM' 'TRANS_FILE' 'SEARCH_RULETYPE'`
# get rules from sesearch
local ssearch="sesearch $SESEARCH_OPTS --$SEARCH_RULETYPE -s $SCONTEXT -t $TCONTEXT -c $CLASS $POLICY_PATH"
`rlSELogVar ssearch`
local RULES=$(eval $ssearch) || let result++
[[ -n "$DEBUG" ]] && echo "ALL RULES${LF}$RULES"
# filter rules specificly for type_transition
if [[ -n "$TRANS_FILE" ]]; then
# TODO: setools v4.1 quotes all filenames, v4.2 only those with spaces
RULES=$(echo "$RULES" | grep -E "\<${TRANS_FILE}\>")
fi
# filter rules according to BOOLEAN
if [[ "$BOOLEAN" == "" ]]; then
# filter out all rules depending on booleans
rlLogDebug "$FUNCNAME(): excluding conditional rules"
rlLogDebug "$FUNCNAME(): EXECUTING echo \"\$RULES\" | grep -v '\['"
RULES=$(echo "$RULES" | grep -v '\[')
rlLogDebug "$FUNCNAME(): RULES${LF}$RULES"
elif [[ "$BOOLEAN" != ".*" ]]; then
# filter out rules without boolean $BOOLEAN
rlLogDebug "$FUNCNAME(): pick only conditional rules using '$BOOLEAN'"
while [[ -n "$BOOLEAN" ]]; do
BOOLEAN="$(rlSETranslateBoolean "$BOOLEAN")"
rlLogDebug "$FUNCNAME(): EXECUTING echo \"\$RULES\" | grep -E \"\[.*\<${BOOLEAN}\>.*\]\""
RULES=$(echo "$RULES" | grep -E "\[.*\<${BOOLEAN}\>.*\]")
BOOLEAN=( "${BOOLEAN[@]:1}" )
done
rlLogDebug "$FUNCNAME(): RULES${LF}$RULES"
[[ -n "$TF" ]] && {
rlLogDebug "$FUNCNAME(): pick only $TF rules"
rlLogDebug "$FUNCNAME(): EXECUTING echo \"\$RULES\" | grep -P \"^[ED]$TF\""
RULES=$(echo "$RULES" | grep -P "^[ED]$TF")
rlLogDebug "$FUNCNAME(): RULES${LF}$RULES"
}
fi
echo "FILTERED RULES${LF}$RULES"
# loop over permissions
while [[ -n "$PERM" ]]; do
local SEARCHFOR="\<${RULETYPE}\>.+\<${CLASS}\>.+\<${PERM}\>"
`rlSELogVar SEARCHFOR`
echo "$RULES" | grep -Eq "$SEARCHFOR"
rlAssertEquals " check permission '$PERM' is present" $? $expected_result || let result++
PERM=( "${PERM[@]:1}" )
done
return $result
}
}
true <<'=cut'
=pod
=head2 rlSEMatchPathCon
Runs matchpathcon and checks if matchpathcon called on PATH returns context which
contains the STRING preceeded by colon. The function prints the result of matchpathcon
anyway.
If variable COLLECTIONS exists, the function will test all collection paths
containing requested path.
rlSEMatchPathCon "/var/www/html" "http_sys_content_t"
=cut
function rlSEMatchPathCon() {
if [ -e /var/run/nomatchpathcon ]; then
echo "rlSEMatchPathCon disabled by /var/run/nomatchpathcon"
return
fi
local FILE_PATH
local LINK_PATH
local REAL_TYPE=`rlSETranslateAlias $2`
rlLogDebug "$FUNCNAME(): REAL_TYPE=$REAL_TYPE"
FILE_PATH=$1
if ! rlIsRHEL '<6'; then
local CL=( $COLLECTIONS )
while [[ -n "$CL" ]]; do
local p="/opt/rh/${CL}/root$1"
[[ -e "$p" ]] && FILE_PATH=( "${FILE_PATH[@]}" "$p" )
CL=( "${CL[@]:1}" )
done
fi
rlLogDebug "$FUNCNAME(): FILE_PATH=$FILE_PATH"
local ec=0
while [[ -n "$FILE_PATH" ]]; do
if [ -L ${FILE_PATH} ] ; then
rlLogDebug "$FUNCNAME(): evaluating symlink"
LINK_PATH=`readlink -f ${FILE_PATH}`
local out="$(matchpathcon ${FILE_PATH} ${LINK_PATH})"
echo "$out"
echo "$out" | grep -q ":${REAL_TYPE}:"
rlAssert0 "Results of matchpathcon ${FILE_PATH} ${LINK_PATH} should contain ${REAL_TYPE}" $? || ec=1
else
rlLogDebug "$FUNCNAME(): evaluating file"
local out="$(matchpathcon ${FILE_PATH})"
echo "$out"
echo "$out" | grep -q ":${REAL_TYPE}:"
rlAssert0 "Result of matchpathcon ${FILE_PATH} should contain ${REAL_TYPE}" $? || ec=1
fi
FILE_PATH=( "${FILE_PATH[@]:1}" )
done
return $ec
}
__INTERNAL_rlSEgetsebool() {
getsebool $(rlSETranslateBoolean ${1})
}
__INTERNAL_rlSEGetBooleanName() {
__INTERNAL_rlSEgetsebool ${1} | cut -d ' ' -f 1
}
# boolean name current persistent
# returns <BOOLEAN> <on|off> <on|off>
__INTERNAL_rlSEBooleanState() {
local tmp=''
local tmp2=''
local boolean="$(rlSETranslateBoolean "$1")"
tmp="$(paste <(getsebool ${boolean:--a} | sort) <(seinfo -b$boolean -x $__INTERNAL_POLICY_FILE 2>/dev/null | grep -iE 'true|false' | tr 'A-Z' 'a-z' | sed -r 's/\<false\>/off/g;s/\<true\>/on/g' | sort))"
rlLogDebug "$FUNCNAME(): $tmp"
tmp2="$(echo "$tmp" | sed -r 's/^(\S+).*\<(on|off)\>.*\<(on|off)\>.*/\1 \2 \3/')"
rlLogDebug "$FUNCNAME(): $tmp2"
while read -r line; do
[[ "$line" =~ ^[a-zA-Z_0-9]+\ +(on|off)\ +(on|off)$ ]] || {
rlLogError "error parsing boolean info '$line', expected format is BOOLEAN (on|off) (on|off)"
rlLogError " first state is a current state got from getsebool, second state is a persistent state got from seinfo"
}
done <<< "$tmp2"
echo "$tmp2"
}
true <<'=cut'
=pod
=head2 rlSEBooleanBackup
The function to backup both current and default state of all or specified the
SELinux booleans, which is then restored by rlSEBooleanRestore function.
rlSEBooleanBackup [--namespace NAMESPACE] [BOOLEAN ...]
=cut
# Usage: rlSEBooleanBackup
function rlSEBooleanBackup() {
local NAMESPACE="$__INTERNAL_rlSE_NAMESPACE"
while [[ "${1:0:1}" == "-" ]]; do
case $1 in
--namespace)
shift
NAMESPACE="$1"
;;
esac
shift
done
local STATUSFILE="$BEAKERLIB_DIR/sebooleans${NAMESPACE}"
local res=0
if [[ -z "$1" ]]; then
if [ -f $STATUSFILE ]; then
rlLogError "$FUNCNAME: Backup file already exists. Backing up all the booleans would erase the current backup."
return 33
fi
touch $STATUSFILE
rlLog "$FUNCNAME: Backing up the current state of all the SELinux booleans"
__INTERNAL_rlSEBooleanState > $STATUSFILE
res=$?
else
# if we didn't save the status yet, save it now
local BOOLEAN
while [[ -n "$1" ]]; do
BOOLEAN="$1"
touch "$STATUSFILE"
if ! grep -q "^$BOOLEAN " "$STATUSFILE"; then
local tmp="$(__INTERNAL_rlSEBooleanState $BOOLEAN)"
[[ -n "$tmp" ]] && echo "$tmp" >> "$STATUSFILE" || let res++
fi
shift
done
fi
return $res
}
true <<'=cut'
=pod
=head2 rlSEBooleanOn, rlSEBooleanOff
The functions to switch SELinux booleans (current or default value using -P) on/off. When executed for the first time, it remembers the initial status which is restored later on by rlSEBooleanRestore.
rlSEBooleanOn [--namespace NAMESPACE] [-P] boolean1 [boolean2 ...]
rlSEBooleanOff [--namespace NAMESPACE] [-P] boolean1 [boolean2 ...]
=cut
rlSEBooleanOn() {
if [ -z "$1" ]; then
rlLogError "$FUNCNAME: Missing arguments"
return 1
fi
local PERMANENT=''
local PERMANENT_MSG=''
while [[ "${1:0:1}" == "-" ]]; do
case $1 in
--namespace)
shift
local __INTERNAL_rlSE_NAMESPACE="$1"
;;
-P)
PERMANENT=' -P '
PERMANENT_MSG=" permanently"
;;
esac
shift
done
if [ -z "$1" ]; then
rlLogError "$FUNCNAME: Missing arguments"
return 1
fi
local new_states=''
local FAILURES=0
rlLog "$FUNCNAME: Setting SELinux booleans$PERMANENT_MSG on: $*"
while [ -n "$1" ]; do
local BOOLEAN=$(__INTERNAL_rlSEGetBooleanName "$1")
[[ -z "$BOOLEAN" ]] && {
rlLogError "$FUNCNAME: boolean name '$1' not found."
FAILURES=$(( $FAILURES + 1 ))
shift
continue
}
[[ "$1" != "$BOOLEAN" ]] && {
rlLogInfo "$FUNCNAME: using boolean name '$BOOLEAN' instead of '$1'"
}
# backup current state
if ! rlSEBooleanBackup "$BOOLEAN"; then
FAILURES=$(( $FAILURES + 1 ))
rlLogError "$FUNCNAME: Backing up the $BOOLEAN boolean failed"
fi
new_states+="$BOOLEAN=on "
shift
done
[[ -n "$new_states" ]] && {
setsebool $PERMANENT $new_states || {
FAILURES=$(( $FAILURES + 1 ))
rlLogError "$FUNCNAME: Setting boolean(s) to true failed"
}
}
return $FAILURES
}
rlSEBooleanOff() {
if [ -z "$1" ]; then
rlLogError "$FUNCNAME: Missing arguments"
return 1
fi
local PERMANENT=''
local PERMANENT_MSG=''
while [[ "${1:0:1}" == "-" ]]; do
case $1 in
--namespace)
shift
local __INTERNAL_rlSE_NAMESPACE="$1"
;;
-P)
PERMANENT=' -P '
PERMANENT_MSG=" permanently"
;;
esac
shift
done
if [ -z "$1" ]; then
rlLogError "$FUNCNAME: Missing arguments"
return 1
fi
local new_states=''
local FAILURES=0
rlLog "$FUNCNAME: Setting SELinux booleans$PERMANENT_MSG off: $*"
while [ -n "$1" ]; do
local BOOLEAN=$(__INTERNAL_rlSEGetBooleanName "$1")
[[ -z "$BOOLEAN" ]] && {
rlLogError "$FUNCNAME: boolean name '$1' not found."
FAILURES=$(( $FAILURES + 1 ))
shift
continue
}
[[ "$1" != "$BOOLEAN" ]] && {
rlLogInfo "$FUNCNAME: using boolean name '$BOOLEAN' instead of '$1'"
}
# backup current state
if ! rlSEBooleanBackup "$BOOLEAN"; then
FAILURES=$(( $FAILURES + 1 ))
rlLogError "$FUNCNAME: Backing up the $BOOLEAN boolean failed"
fi
new_states+="$BOOLEAN=off "
shift
done
[[ -n "$new_states" ]] && {
setsebool $PERMANENT $new_states || {
FAILURES=$(( $FAILURES + 1 ))
rlLogError "$FUNCNAME: Setting boolean(s) to false failed"
}
}
return $FAILURES
}
true <<'=cut'
=pod
=head2 rlSEBooleanRestore
Restores the original state of SELinux boolean(s) backed up by rlSEBooleanOn/Off
or rlSEBooleanBackup - all backed up booleans if none specified.
If there was no boolean backed up; either by rlSEBooleanOn/Off or
rlSEBooleanBackup the function logs an error and returns code 99.
rlSEBooleanRestore [--namespace NAMESPACE] [boolean1 ...]
=cut
function rlSEBooleanRestore() {
local NAMESPACE='' tmp
while [[ "${1:0:1}" == "-" ]]; do
case $1 in
--namespace)
shift
NAMESPACE="$1"
;;
esac
shift
done
local FAILURES=0
if [ ! -f $BEAKERLIB_DIR/sebooleans${NAMESPACE} ]; then
rlLogError "$FUNCNAME: cannot restore SELinux booleans, saved states are not available"
return 99
fi
local STATUSFILE="$(cat $BEAKERLIB_DIR/sebooleans${NAMESPACE})"
local CHANGED_BOOLEANS=''
local CURRENT_STATES=''
local persistent_states=''
local current_states=''
local BOOLEAN
# populate booleans list and current states
if [ -z "$1" ]; then # no booleans specified, restoring all booleans
rlLog "$FUNCNAME: restoring all backed up SELinux booleans"
CURRENT_STATES="$(__INTERNAL_rlSEBooleanState)"
CHANGED_BOOLEANS="$(diff <( echo "$STATUSFILE" ) <( echo "$CURRENT_STATES" ) | grep '<' | sed 's/< //' | awk '{ print $1 }')"
else # restoring only specified booleans
rlLog "$FUNCNAME: restoring original status of SELinux booleans: $*"
while [ -n "$1" ]; do # process all passed booleans
BOOLEAN=$(__INTERNAL_rlSEGetBooleanName "$1")
[[ -z "$BOOLEAN" ]] && {
let FAILURES++
rlLogError "$FUNCNAME: cannot restore SELinux boolean $1, which does not exist!"
shift
continue
}
rlLogDebug "$FUNCNAME(): using actual boolean name '$BOOLEAN'"
CURRENT_STATES+="$(__INTERNAL_rlSEBooleanState $BOOLEAN)"$'\n'
CHANGED_BOOLEANS+="$BOOLEAN"$'\n'
shift
done
fi
# create lists of pesistent and current states to restore
while read BOOLEAN; do
[[ -z "$BOOLEAN" ]] && continue
`rlSELogVar BOOLEAN`
tmp="\<${BOOLEAN} (\S+) (\S+)"
if ! [[ "$STATUSFILE" =~ $tmp ]]; then
let FAILURES++
rlLogError "$FUNCNAME: cannot restore SELinux boolean $BOOLEAN, original state was not saved"
shift
continue
fi
BACKUP_STATE="${BASH_REMATCH[1]}"
BACKUP_DEFAULT_STATE="${BASH_REMATCH[2]}"
`rlSELogVar BASH_REMATCH BACKUP_STATE BACKUP_DEFAULT_STATE`
if ! [[ "$CURRENT_STATES" =~ $tmp ]]; then
let FAILURES++
rlLogError "$FUNCNAME: cannot restore SELinux boolean $BOOLEAN, current state not available"
shift
continue
fi
CURRENT_STATE="${BASH_REMATCH[1]}"
CURRENT_DEFAULT_STATE="${BASH_REMATCH[2]}"
`rlSELogVar BASH_REMATCH CURRENT_STATE CURRENT_DEFAULT_STATE`
if [[ "$CURRENT_DEFAULT_STATE" != "$BACKUP_DEFAULT_STATE" ]]; then
persistent_states+="$BOOLEAN=$BACKUP_DEFAULT_STATE "
current_states+="$BOOLEAN=$BACKUP_STATE "
CURRENT_STATE=$BACKUP_DEFAULT_STATE
fi
if [[ "$CURRENT_STATE" != "$BACKUP_STATE" ]]; then
current_states+="$BOOLEAN=$BACKUP_STATE "
fi
done <<< "$CHANGED_BOOLEANS"
# do the actual restoration
[[ -n "$persistent_states" ]] && {
rlLogInfo "$FUNCNAME: restoring boolean persistent states"
rlLogDebug "$FUNCNAME(): $persistent_states"
setsebool -P $persistent_states || {
let FAILURES++
rlLogError "$FUNCNAME: setting boolean(s) failed"
}
}
[[ -n "$current_states" ]] && {
rlLogInfo "$FUNCNAME: restoring boolean current states"
rlLogDebug "$FUNCNAME(): $current_states"
setsebool $current_states || {
let FAILURES++
rlLogError "$FUNCNAME: setting boolean(s) failed"
}
}
return $FAILURES
}
true <<'=cut'
=pod
=head2 rlSEMatchPortCon
Runs seinfo and checks if a SELinux port of type $1 (tcp/udp) and of number $2 is known under context type $3. Required parameters and their order: port type, port number, SELinux context type.
rlSEMatchPortCon PROTO NUMBER TYPE
rlSEMatchPortCon tcp 80 "http_port_t"
=cut
function rlSEMatchPortCon() {
if [ -e /var/run/nomatchportcon ]; then
echo "rlSEMatchPortCon disabled by /var/run/nomatchportcon"
return
fi
if [[ ${SETOOLS_MAJOR_VERSION} == "4" ]] ; then
# setools v.4
rlRun "seinfo --portcon=$2 | grep \"portcon $1 .*:$3\""
elif rlIsRHEL 5 ; then
rlRun "seinfo -l$1 -p$2 | grep \"portcon.*:$3\""
else
rlRun "seinfo --protocol=$1 --portcon=$2 | grep \"portcon.*:$3\""
fi
}
true <<'=cut'
=pod
=head2 rlSEPortAdd
# Add context type to given port if not in the current policy. The current state of the port contexts is backed up and can be restored by rlSEPortRestore.
rlSEPortAdd PROTO NUMBER CONTEXT_TYPE
=cut
function rlSEPortAdd() {
local RES
local NAMESPACE=''
while [[ "${1:0:1}" == "-" ]]; do
case $1 in
--namespace)
shift
NAMESPACE="_$1"
;;
esac
shift
done
if [[ ${SETOOLS_MAJOR_VERSION} == "4" ]] ; then
# setools v.4
seinfo --portcon=$2 | grep "portcon $1 .*:$3"
RES=$?
elif rlIsRHEL 5 ; then
seinfo -l$1 -p$2 | grep "portcon $1 $2 .*:$3"
RES=$?
else
seinfo --protocol=$1 --portcon=$2 | grep "portcon $1 .*$2.*:$3"
RES=$?
fi
if [ $RES -eq 0 ]; then
rlLog "$FUNCNAME: The type $3 of $1 port $2 already in policy"
else
if [[ ${SETOOLS_MAJOR_VERSION} == "4" ]] ; then
# setools v.4
seinfo --portcon=$2 | grep "portcon $1 \<$2\>"
RES=$?
elif rlIsRHEL 5 ; then
seinfo -l$1 -p$2 | grep 'portcon $1 \<$2\>'
RES=$?
else
seinfo --protocol=$1 --portcon=$2 | grep "portcon $1 \<$2\>"
RES=$?
fi
local CHANGED_PORT_CONTEXTS="$(__INTERNAL_ST_GET ${NAMESPACE:+--namespace "$NAMESPACE"} rlSE_CHANGED_PORT_CONTEXTS)"
if [ $RES -eq 0 ]; then
rlLog "$FUNCNAME: Setting context type $3 to $1 port $2"
rlRun "semanage port -m -t $3 -p $1 $2"
if [ $? -eq 0 ]; then
CHANGED_PORT_CONTEXTS="$CHANGED_PORT_CONTEXTS $1:$2:$3"
fi
else
rlLog "$FUNCNAME: Setting context type $3 to $1 port $2"
rlRun "semanage port -a -t $3 -p $1 $2"
if [ $? -eq 0 ]; then
CHANGED_PORT_CONTEXTS="$CHANGED_PORT_CONTEXTS $1:$2:$3"
fi
fi
__INTERNAL_ST_PUT ${NAMESPACE:+--namespace "$NAMESPACE"} rlSE_CHANGED_PORT_CONTEXTS "$CHANGED_PORT_CONTEXTS"
fi
}
true <<'=cut'
=pod
=head2 rlSEPortRestore
# Rollbacks port contexts set by rlSEPortAdd function. Restore all if not specified.
rlSEPortRestore
=cut
function rlSEPortRestore() {
local ITEM
local TYPE
local PROTO
local PORT
local NEWLIST
local NAMESPACE=''
while [[ "${1:0:1}" == "-" ]]; do
case $1 in
--namespace)
shift
NAMESPACE="_$1"
;;
esac
shift
done
local CHANGED_PORT_CONTEXTS=$(__INTERNAL_ST_GET ${NAMESPACE:+--namespace "$NAMESPACE"} rlSE_CHANGED_PORT_CONTEXTS)
if [ ! $# -lt 1 ]; then
NEWLIST=""
for ITEM in $CHANGED_PORT_CONTEXTS; do
if [ $ITEM == "$1:$2:$3" ]; then
rlLog "$FUNCNAME: Removing context type $3 to $1 port $2"
rlRun "semanage port -d -t $3 -p $1 $2"
else
NEWLIST="$NEWLIST $ITEM"
fi
done
CHANGED_PORT_CONTEXTS=$NEWLIST
else
for ITEM in $CHANGED_PORT_CONTEXTS; do
TYPE=`echo $ITEM | cut -f 3 -d ':'`
PROTO=`echo $ITEM | cut -f 1 -d ':'`
PORT=`echo $ITEM | cut -f 2 -d ':'`
rlLog "$FUNCNAME: Removing context type $TYPE to $PROTO port $PORT"
rlRun "semanage port -d -t $TYPE -p $PROTO $PORT"
done
CHANGED_PORT_CONTEXTS=""
fi
__INTERNAL_ST_PUT ${NAMESPACE:+--namespace "$NAMESPACE"} rlSE_CHANGED_PORT_CONTEXTS "$CHANGED_PORT_CONTEXTS"
}
true <<'=cut'
=pod
=head2 rlSESetTimestamp, rlSECheckAVC, rlSEAVCCheck
Pair functions to check AVC messages from the given moment. The starting timestamps can be named to be able to mark more than one moment.
# Beginning of the phase/test:
rlSESetTimestamp [timestamp_name]
# End of the phase/test, count only unignored AVCs/ERRORs:
rlSECheckAVC [--no-ignore] [--ignore REG_EXP [--ignore REG_EXP]] [timestamp_name]
# or check without assert, prints number of unignored and unexpected AVCs/ERRORs, returns 0 if no unignored AVC/ERROR found:
rlSEAVCCheck [--no-ignore] [--ignore REG_EXP [--ignore REG_EXP]] [--expect REG_EXP [--expect REG_EXP]] [timestamp_name]
=cut
function rlSESetTimestamp() {
local STAMP=`LC_ALL=en_US.UTF-8 date "+%x %T"`
local NAME="TIMESTAMP"
[ -z "$1" ] || NAME="${NAME}_$1"
eval "export __INTERNAL_rlSE_$NAME='$STAMP'"
rlLog "$FUNCNAME: Setting timestamp '$NAME' [$STAMP]"
}
function rlSECheckAVC() {
rlSEAVCCheck "$@" > /dev/null
rlAssert0 'Check there are no unexpected AVCs/ERRORs' $?
}
function rlSEAVCCheck() {
local NAME="TIMESTAMP"
local ignore=('type=USER_AVC.*received (policyload|setenforce) notice')
local ignore_internal='type=(SYSCALL|PATH|CWD|PROCTITLE|SOCKETCALL|OBJ_PID|SOCKADDR)'
local expect
rlLogDebug "$FUNCNAME(): process options"
local GETOPT=$(getopt -q -o ni:e: -l no-ignore,ignore:,expect:,expected: -- "$@")
eval set -- "$GETOPT"
while [[ -n "$@" ]]; do
case $1 in
--)
shift; break
;;
-n|--no-ignore)
ignore=()
;;
-i|--ignore)
shift
ignore+=("$1")
;;
-e|--expect|--expected)
shift
expect+=("$1")
;;
*)
echo "unknown option '$1'"
return 1
;;
esac
shift;
done
[ -z "$1" ] || NAME="${NAME}_$1"
local STAMP
eval "STAMP=\"\$__INTERNAL_rlSE_$NAME\""
`rlSELogVar expect ignore ignore_internal STAMP`
if [ -z "$STAMP" ]; then
rlLogError "$FUNCNAME: Timestamp $NAME is not defined"
return 1
else
rlLog "$FUNCNAME: Search for AVCs, USER_AVCs, SELINUX_ERRs, and USER_SELINUX_ERRs since timestamp '$NAME' [$STAMP]"
local ausearch_output=$(LC_ALL='en_US.UTF-8' ausearch --input-logs -i -m AVC -m USER_AVC -m SELINUX_ERR -m USER_SELINUX_ERR -ts $STAMP 2>&1)
echo "$ausearch_output" >&2
local res=0
# filter out ignored patterns
[[ -n "${ignore[*]}" ]] && {
rlLogInfo "$FUNCNAME: ignoring patterns:"
local i
for i in "${ignore[@]}"; do
rlLogInfo "$FUNCNAME: $i"
ignore_internal="$ignore_internal|$i"
done
}
rlLogDebug "$FUNCNAME(): filter out ignored patterns"
ausearch_output="$(echo "$ausearch_output" | grep -Pi '^type=' | grep -vP "$ignore_internal")"
`rlSELogVar ausearch_output`
# find expected and filter then out; if not found, count them
local exp
[[ -n "${expect[*]}" ]] && rlLogInfo "$FUNCNAME: looking for expected patterns:"
for exp in "${expect[@]}" ; do
if echo "$ausearch_output" | grep -qP "$exp" ; then
rlLogInfo "$FUNCNAME: ok ... $exp"
ausearch_output="$(echo "$ausearch_output" | grep -vP "$exp")"
else
rlLogWarning "$FUNCNAME: fail ... $exp"
let res++
fi
done
`rlSELogVar res ausearch_output`
# count the rest
if [[ -n "$ausearch_output" ]] ; then
let res+=$(echo "$ausearch_output" | wc -l)
echo "---==============---" >&2
echo "UNEXPECTED MESSAGES:" >&2
echo "$ausearch_output" >&2
echo "---==============---" >&2
fi
`rlSELogVar res`
echo "$res"
[[ "$res" -eq 0 ]]
return $?
fi
}
true <<'=cut'
=pod
=head2 rlSEIsMLS, rlSEIsTargeted, rlSEIsStrict, rlSEIsMinimum
The functions to check if desired policy is active at the moment. Returns 0 if true.
=cut
# tests whether MLS policy is used
function rlSEIsMLS() {
sestatus | grep -qi mls$
}
# tests whether targeted policy is used
function rlSEIsTargeted() {
sestatus | grep -qi targeted$
}
# tests whether strict policy is used
function rlSEIsStrict() {
sestatus | grep -qi strict$
}
# tests whether minimum policy is used
function rlSEIsMinimum() {
sestatus | grep -qi minimum$
}
true <<'=cut'
=pod
=head2 rlSEDefined
The functions to check if given types exist in the current policy and returns 0 if true.
rlSEDefined httpd_t snmpd_t
=cut
# Searches for all the types given in the argument and returns 0 if all
# of them exist. Returns 1 otherwise.
function rlSEDefined() {
local RET_VAL=0
local TYPE
for TYPE in $1 ; do
# there are also type aliases in RHEL-7
if seinfo -t$TYPE 2>&1 | grep -qi -e error -e 'types: 0' ; then
echo "$TYPE is NOT defined"
RET_VAL=1
else
echo "$TYPE is defined"
fi
done
return $RET_VAL;
}
true <<'=cut'
=pod
=head2 rlSEStatus
Prints various SELinux information
rlSEStatus # prints sestatus results
rlSEStatus -b [regexp] # prints all/selected booleans (from getsebool)
rlSEStatus -p [regexp] # prints all/selected ports (from semanage port -l)
rlSEStatus -m [regexp] # prints all/selected selinux modules (from semodule -l)
rlSEStatus -em [regexp] # prints all/selected enabled selinux modules
rlSEStatus -dm [regexp] # prints all/selected disabled selinux modules
rlSEStatus -t [regexp] # prints all/selected context types (from seinfo -t)
=cut
function rlSEStatus() {
# -b parameter - print booleans
if [ "$1" = "-b" ]; then
rlRun "getsebool -a | grep \"$2\""
return;
fi
# -p parameter - print ports
if [ "$1" = "-p" ]; then
rlRun "semanage port -l | grep \"$2\""
return;
fi
# -m parameter - print selinux modules
if [ "$1" = "-m" ]; then
rlRun "semodule -l | grep \"$2\""
return;
fi
# -em parameter - print enabled selinux modules
if [ "$1" = "-em" ]; then
rlRun "semodule -l | grep -v -i disabled | grep \"$2\""
return;
fi
# -dm parameter - print disabled selinux modules
if [ "$1" = "-dm" ]; then
rlRun "semodule -l | grep -i disabled | grep \"$2\""
return;
fi
# -t parameter - print types
if [ "$1" = "-t" ]; then
rlRun "seinfo -t | grep \"$2\""
return;
fi
# Without any params - run sestatus
rlRun "id -Z"
rlRun "sestatus"
if semodule --help | grep -qi "list-modules.*kind" ; then
rlRun "semodule --list-modules=full | grep -i disabled" 0,1
else
rlRun "semodule -l | grep -i disabled" 0,1
fi
}
true <<'=cut'
=pod
=head2 rlSERunWithPassword
The function executes given command and provides the password whenever prompted for password.
The password can be either stored in PASSWORD global variable or specified using -p parameter.
rlSERunWithPassword [ -p PASSWORD ] COMMAND
=cut
function rlSERunWithPassword() {
local PASS="$PASSWORD"
# read password parameter
if [ "$1" = "-p" ]; then
PASS="$2"
shift 2
fi
cat <<EOF | expect -
set timeout 5
spawn $*
expect {
-re "assword:" { send "$PASS\r"; exp_continue }
}
EOF
}
true <<'=cut'
=pod
=head2 rlSEService
Runs given service using run_init tool and checks the context of the running process.
Usage:
rlSEService ${ROOT_PASSWORD} ${SERVICE_NAME} ${PROCESS_NAME} ${PROCESS_CONTEXT} ${OPERATIONS}
Example:
rlSEService "root_passwd" "httpd" "httpd" "httpd_t" "start status stop status"
=cut
function rlSEService() {
local OPERATION
local RET_VAL
local REAL_TYPE
for OPERATION in $5; do
# Set the return value
# Service status can return both 0 and 3
if echo $OPERATION | grep -q status; then
RET_VAL=0,1,3
else
RET_VAL=0
fi
# Run the service using run_init tool
#rlRun "echo $1 | run_init service $2 $OPERATION" $RET_VAL
if rlIsRHEL 5 6 ; then
if rlSEIsMLS ; then
rlRun "rlSERunWithPassword -p $1 run_init service $2 $OPERATION" $RET_VAL
elif rlSEIsStrict ; then
rlRun "rlSERunWithPassword -p $1 run_init service $2 $OPERATION" $RET_VAL
else
rlRun "service $2 $OPERATION" $RET_VAL
fi
else
rlRun "service $2 $OPERATION" $RET_VAL
fi
REAL_TYPE=`rlSETranslateAlias $4`
# Check context of the service process
if echo $OPERATION | grep -q -e start -e reload ; then
sleep $6
if [ "$3" != "-" ] ; then
rlRun "ps -efZ | grep -v \" grep \" | grep -E \"$3\""
rlRun "ps -efZ | grep -v \" grep \" | grep -E \"${REAL_TYPE}.*$3\""
fi
fi
# Wait given number of seconds
if [ ! -z $6 ]; then
sleep $6
fi
done
}
function rlSETransAndRun() {
local TMPDIR
local SELINUX_TYPE
local CURRENT_CONTEXT
if [ -f testpolicy.te ] ; then
rlLog "$FUNCNAME: using a predefined local policy for the test"
rlRun "ls -l testpolicy.*"
rlRun "make -f /usr/share/selinux/devel/Makefile testpolicy.pp"
rlRun "semodule -i testpolicy.pp"
rm -rf tmp # by-product of the local policy module compilation
else
TMPDIR=`mktemp -d`
SELINUX_TYPE=`echo $1 | cut -d : -f 3`
CURRENT_CONTEXT=`id -Z`
rlLog "$FUNCNAME: preparing a local policy which allows transition from $CURRENT_CONTEXT to $1"
pushd ${TMPDIR}
echo -e "policy_module(testpolicy,1.0)\n\nrequire {\n type unconfined_t; type $SELINUX_TYPE;\n class process { transition };\n}\n\nallow unconfined_t $SELINUX_TYPE : process { transition };\n" > testpolicy.te
rlRun "make -f /usr/share/selinux/devel/Makefile"
rlRun "semodule -i testpolicy.pp"
popd
rm -rf ${TMPDIR}
fi
rlRun "runcon $1 bash -c \"$2\""
rlRun "semodule -r testpolicy"
}
true <<'=cut'
=pod
=head2 rlSERunWithContext
Executes command with a given context. There has to be transition allowed from initrc_exec_t to a desired context available.
rlSERunWithContext [-u USER] [-r ROLE] [-t TYPE] cmd
e.g. rlRun "rlSERunWithContext -t smbd_exec_t stat $TmpDir/testmount"
=cut
# OPTIONS are supposed to be passed to chcon command
function rlSERunWithContext() {
local TMPDIR
local OPTIONS
local COMMAND
local RETCODE
#read and store options
while [ "${1:0:1}" = "-" ]; do
OPTIONS+=" $1 $2"
shift 2
done
if [ -z "$1" ]; then
rlLogError "$FUNCNAME: No command passed to rlRunWithSELinuxContext"
return 99
else
COMMAND=$@ # store command with arguments
fi
TMPDIR=`mktemp -d`
chcon -t smbd_tmp_t $TMPDIR
#prepare launcher scripts - one with initrc_exec_t and other with desired context
echo -e "#!/bin/bash\n$TMPDIR/launcher2.sh\n" > $TMPDIR/launcher1.sh
echo -e "#!/bin/bash\n$COMMAND" > $TMPDIR/launcher2.sh
chcon -t initrc_exec_t $TMPDIR/launcher1.sh
chcon $OPTIONS $TMPDIR/launcher2.sh
chmod a+x $TMPDIR/*.sh
#cat $TMPDIR/launcher1.sh
#cat $TMPDIR/launcher2.sh
$TMPDIR/launcher1.sh # execute the first launcher script
RETCODE=$?
rm -rf $TMPDIR
return $RETCODE
}
function rlSEConfigureSSH () {
rlFileBackup /etc/ssh/sshd_config
rlRun "sed -i 's/^.*PermitRootLogin.*$/PermitRootLogin yes/' /etc/ssh/sshd_config"
rlRun "sed -i 's/^.*PasswordAuthentication.*$/PasswordAuthentication yes/' /etc/ssh/sshd_config"
rlRun "service sshd restart"
}
true <<'=cut'
=pod
=head2 rlSETranslateBoolean
Some booleans were renamed during the time, but already written tests contain
boolean names which may not be current. The function takes a boolean name as an argument.
When executed on RHEL-5 or RHEL-6 the function returns old boolean name.
When executed on RHEL >= 7 the function returns new boolean name.
If the boolean was not renamed then the same boolean is returned.
rlSETranslateBoolean BOOL_NAME
=cut
function rlSETranslateBoolean() {
local FIELD LINE RENAMED_BOOLEANS
if rlIsRHEL 5 > /dev/null 2>&1; then
FIELD=2
elif rlIsRHEL 6 > /dev/null 2>&1; then
if [[ $1 == "clamscan_can_scan_system" ]] ||
[[ $1 == "antivirus_can_scan_system" ]] ||
[[ $1 == "amavis_use_jit" ]] ||
[[ $1 == "clamd_use_jit" ]] ||
[[ $1 == "antivirus_use_jit" ]] ; then
FIELD=1
else
FIELD=2
fi
else
FIELD=1
fi
RENAMED_BOOLEANS="
antivirus_can_scan_system clamscan_can_scan_system
antivirus_use_jit amavis_use_jit
antivirus_use_jit clamd_use_jit
auditadm_exec_content allow_auditadm_exec_content
condor_tcp_network_connect condor_domain_can_network_connect
cvs_read_shadow allow_cvs_read_shadow
daemons_dump_core allow_daemons_dump_core
daemons_use_tcp_wrapper allow_daemons_use_tcp_wrapper
daemons_use_tty allow_daemons_use_tty
domain_fd_use allow_domain_fd_use
ftpd_anon_write allow_ftpd_anon_write
ftpd_full_access allow_ftpd_full_access
ftpd_use_cifs allow_ftpd_use_cifs
ftpd_use_nfs allow_ftpd_use_nfs
gssd_read_tmp allow_gssd_read_tmp
guest_exec_content allow_guest_exec_content
httpd_anon_write allow_httpd_anon_write
httpd_mod_auth_ntlm_winbind allow_httpd_mod_auth_ntlm_winbind
httpd_mod_auth_pam allow_httpd_mod_auth_pam
httpd_sys_script_anon_write allow_httpd_sys_script_anon_write
icecast_use_any_tcp_ports icecast_connect_any
kerberos_enabled allow_kerberos
login_console_enabled allow_console_login
logwatch_can_network_connect_mail logwatch_can_sendmail
mount_anyfile allow_mount_anyfile
mplayer_execstack allow_mplayer_execstack
named_tcp_bind_http_port named_bind_http_port
nfsd_anon_write allow_nfsd_anon_write
nis_enabled allow_ypbind
polyinstantiation_enabled allow_polyinstantiation
postfix_local_write_mail_spool allow_postfix_local_write_mail_spool
postgresql_can_rsync sepgsql_enable_pitr_implementation
postgresql_selinux_transmit_client_label sepgsql_transmit_client_label
postgresql_selinux_unconfined_dbadm sepgsql_unconfined_dbadm
postgresql_selinux_users_ddl sepgsql_enable_users_ddl
puppetagent_manage_all_files puppet_manage_all_files
rsync_anon_write allow_rsync_anon_write
saslauthd_read_shadow allow_saslauthd_read_shadow
secadm_exec_content allow_secadm_exec_content
selinuxuser_direct_dri_enabled user_direct_dri
selinuxuser_execheap allow_execheap
selinuxuser_execmod allow_execmod
selinuxuser_execstack allow_execstack
selinuxuser_mysql_connect_enabled allow_user_mysql_connect
selinuxuser_ping user_ping
selinuxuser_postgresql_connect_enabled allow_user_postgresql_connect
selinuxuser_rw_noexattrfile user_rw_noexattrfile
selinuxuser_share_music user_share_music
selinuxuser_tcp_server user_tcp_server
smbd_anon_write allow_smbd_anon_write
ssh_keysign allow_ssh_keysign
staff_exec_content allow_staff_exec_content
sysadm_exec_content allow_sysadm_exec_content
user_exec_content allow_user_exec_content
xguest_exec_content allow_xguest_exec_content
xserver_clients_write_xshm allow_write_xshm
xserver_execmem allow_xserver_execmem
zebra_write_config allow_zebra_write_config
"
LINE="$(echo "${RENAMED_BOOLEANS}" | grep -E -- "\<${1}\>")"
if [ $? -eq 0 ] ; then
echo $LINE | cut -d ' ' -f ${FIELD}
else
echo $1
fi
return 0
}
true <<'=cut'
=pod
=head2 rlSETranslateAlias
If first parameter is an alias then the function returns the real type.
If first parameter is a real type then the function returns the real type.
Limitation: seinfo does not report aliases on RHEL-5 and RHEL-6 now.
rlSETranslateAlias ALIAS
=cut
function rlSETranslateAlias {
local res
if rlIsRHEL 5 ; then
rlLogDebug "$FUNCNAME(): RHEL5 - no translation, using $1"
echo $1
return 0
elif rlIsRHEL 6 ; then
rlLogDebug "$FUNCNAME(): RHEL6"
if seinfo -t$1 2>&1 | grep -q "ERROR:" ; then
rlLogDebug "$FUNCNAME(): got an error - no translation, using $1"
echo $1
return 1
elif seinfo -t$1 2>&1 | grep -q " $1" ; then
rlLogDebug "$FUNCNAME(): seinfo confirmed the name - no translation, using $1"
echo $1
return 0
else
rlLogDebug "$FUNCNAME(): the type should be used either as a source or as a target in an allow rule"
res="$(( sesearch -s $1 -A | tr -s ' ' | cut -d ' ' -f 3 ; sesearch -t $1 -A | tr -s ' ' | cut -d ' ' -f 4 ) | sort | uniq | grep "^.*_t$")"
rlLogDebug "$FUNCNAME(): translated name: $res"
echo "$res"
return 0
fi
else
rlLogDebug "$FUNCNAME(): RHEL>6 && Fedora"
rlLogDebug "$FUNCNAME(): setools version ${SETOOLS_MAJOR_VERSION}"
local seinfo_out
seinfo_out="$(seinfo -t$1 2>&1 )"
rlLogDebug "$FUNCNAME(): seinfo_out='$seinfo_out'"
if echo "$seinfo_out" | grep -q "ERROR:" ; then
rlLogDebug "$FUNCNAME(): the type was not recognized - no translation, using $1"
echo $1
return 1
elif echo "$seinfo_out" | grep -q "TypeName " ; then
rlLogDebug "$FUNCNAME(): translation done using TypeName"
res="$(seinfo -t$1 2>/dev/null | grep TypeName | tr -s ' ' | cut -d ' ' -f 3)"
rlLogDebug "$FUNCNAME(): translated name: $res"
echo "$res"
return 0
elif echo "$seinfo_out" | grep -q "Types: 0" ; then
rlLogDebug "$FUNCNAME(): # cannot translate alias, if setools v.4 are used then BZ#1581761 - no translation, using $1"
echo $1
return 1
elif [[ ${SETOOLS_MAJOR_VERSION} == "4" ]] ; then
rlLogDebug "$FUNCNAME(): translation done using seinfo v.4 parsing"
res="$(seinfo -t$1 2>/dev/null | tail -n 1 | tr -s ' ' | cut -d ' ' -f 2)"
rlLogDebug "$FUNCNAME(): translated name: $res"
echo "$res"
return 0
else
rlLogDebug "$FUNCNAME(): translation done using seinfo v.3 parsing"
res="$(seinfo -t$1 2>/dev/null | head -n 1 | tr -s ' ' | cut -d ' ' -f 2)"
rlLogDebug "$FUNCNAME(): translated name: $res"
echo "$res"
return 0
fi
fi
}
true <<'=cut'
=pod
=head2 rlSEListServices
There are many services which by default run on the same port.
The function returns a list of such services which run on given port.
Otherwise the function returns an empty list.
=cut
function rlSEListServices() {
local SERVICES_ARRAY
if [ $1 -lt 1 -o $1 -gt 65535 ] ; then
rlLogError "$FUNCNAME: invalid port number"
return 1
fi
SERVICES_ARRAY[21]='vsftpd proftpd pure-ftpd lighttpd' # FTP servers
SERVICES_ARRAY[25]='exim postfix sendmail' # SMTP servers
SERVICES_ARRAY[53]='named named-sdb unbound yadifad nsd pdns systemd-resolved dnsmasq' # DNS servers
SERVICES_ARRAY[80]='httpd cherokee lighttpd nginx thttpd' # HTTP servers
SERVICES_ARRAY[123]='ntpd chronyd' # NTP servers
echo -n ${SERVICES_ARRAY[$1]}
return 0
}
true <<'=cut'
=pod
=head2 rlSESetEnforce
The functions sets selinux mode to Enforcing unless env. variable ENFORCING is set to 0. Then,
the permissive mode is set. Moreover, when the test is running in permissive mode, the function
thrown a WARN.
=cut
function rlSESetEnforce() {
if [ "x$ENFORCING" != "x0" ]; then
ENFORCING=1
fi
rlRun "setenforce $ENFORCING"
getenforce | grep Permissive && rlLogWarning "$FUNCNAME: The test is running in SELinux permissive mode."
}
__INTERNAL_rlSEModuleList() {
local semodule_list
eval semodule_list="\$($__INTERNAL_SEMODULE_LISTING)";
local res=$?
echo "$semodule_list" | sed -r 's/^[0-9]+\s//'
return $res
}
: <<'=cut'
=pod
=head2 rlSEModuleEnable
The function attempts to enable policy module(s).
rlSEModuleEnable [--namespace NAMESPACE] MODULE ...
Return number of failures
See also rlSEModuleRestore
=cut
rlSEModuleEnable() {
local NAMESPACE='' res=0
while [[ "${1:0:1}" == "-" ]]; do
case $1 in
--namespace)
shift
NAMESPACE="_$1"
;;
esac
shift
done
local module semodule_list to_enable
if semodule_list="$(__INTERNAL_rlSEModuleList)"; then
for module in "$@" ; do
local semodule="$(echo "$semodule_list" | grep -E "^${module}\s")"
if [[ -z "$semodule" ]] ; then
rlLogError "$FUNCNAME: module '$module' does not exist, skipping"
let res++
else
local state="$(echo "$semodule" | grep -iqE '\sdisabled$' && echo disabled || echo enabled)"
[[ -z "$(__INTERNAL_ST_GET ${NAMESPACE:+--namespace "$NAMESPACE"} --section=semodule $module)" ]] && \
__INTERNAL_ST_PUT ${NAMESPACE:+--namespace "$NAMESPACE"} --section=semodule $module $state
__INTERNAL_ST_PUT ${NAMESPACE:+--namespace "$NAMESPACE"} --section=semodule changed_modules "$(__INTERNAL_ST_GET ${NAMESPACE:+--namespace "$NAMESPACE"} --section=semodule changed_modules) $module"
if [[ "$state" == "enabled" ]]; then
rlLogInfo "$FUNCNAME: module '$module' is already enabled"
else
to_enable="$to_enable $module"
fi
fi
done
to_enable="${to_enable:1}"
if [[ -n "$to_enable" ]]; then
rlLog "$FUNCNAME: enabling '$to_enable' module(s), running 'semodule -e $to_enable'"
semodule -e $to_enable
local semodule_res=$?
if [[ $semodule_res -eq 0 ]] ; then
rlLog "$FUNCNAME: semodule enable passed"
else
rlLog "$FUNCNAME: semodule enable failed with exit code '$semodule_res'"
let res++
fi
fi
else
rlLogError "$FUNCNAME: could not get modules list"
let res++
fi
return $res
}
: <<'=cut'
=pod
=head2 rlSEModuleDisable
The function attempts to disable policy module(s).
rlSEModuleDisable [--namespace NAMESPACE] MODULE ...
Return number of failures
See also rlSEModuleRestore
=cut
rlSEModuleDisable() {
local NAMESPACE='' res=0
while [[ "${1:0:1}" == "-" ]]; do
case $1 in
--namespace)
shift
NAMESPACE="_$1"
;;
esac
shift
done
local module semodule_list to_disable
if semodule_list="$(__INTERNAL_rlSEModuleList)"; then
for module in "$@" ; do
local semodule="$(echo "$semodule_list" | grep -E "^${module}\s")"
if [[ -z "$semodule" ]] ; then
rlLogError "$FUNCNAME: module '$module' does not exist, skipping"
let res++
else
local state="$(echo "$semodule" | grep -iqE '\sdisabled$' && echo disabled || echo enabled)"
[[ -z "$(__INTERNAL_ST_GET ${NAMESPACE:+--namespace "$NAMESPACE"} --section=semodule $module)" ]] && \
__INTERNAL_ST_PUT ${NAMESPACE:+--namespace "$NAMESPACE"} --section=semodule $module $state
__INTERNAL_ST_PUT ${NAMESPACE:+--namespace "$NAMESPACE"} --section=semodule changed_modules "$(__INTERNAL_ST_GET ${NAMESPACE:+--namespace "$NAMESPACE"} --section=semodule changed_modules) $module"
if [[ "$state" == "disabled" ]]; then
rlLogInfo "$FUNCNAME: module '$module' is already disabled"
else
to_disable="$to_disable $module"
fi
fi
done
to_disable="${to_disable:1}"
if [[ -n "$to_disable" ]]; then
rlLog "$FUNCNAME: disabling '$to_disable' module(s), running 'semodule -d $to_disable'"
semodule -d $to_disable
local semodule_res=$?
if [[ $semodule_res -eq 0 ]] ; then
rlLog "$FUNCNAME: semodule disable passed"
else
rlLog "$FUNCNAME: semodule disable failed with exit code '$semodule_res'"
let res++
fi
fi
else
rlLogError "$FUNCNAME: could not get modules list"
let res++
fi
return $res
}
: <<'=cut'
=pod
=head2 rlSEModuleRestore
The function attempts to restore policy module(s) state present before
rlSEModuleEnable/rlSEModuleDisable was firstly called.
If no module is specified, all modules will be restored.
rlSEModuleRestore [--namespace NAMESPACE] [MODULE ...]
Return number of failures
=cut
rlSEModuleRestore() {
local NAMESPACE='' res=0
while [[ "${1:0:1}" == "-" ]]; do
case $1 in
--namespace)
shift
NAMESPACE="_$1"
;;
esac
shift
done
local module semodule_list to_enable to_disable
if semodule_list="$(__INTERNAL_rlSEModuleList)"; then
local modules="$@"
local changed_modules="$(__INTERNAL_ST_GET ${NAMESPACE:+--namespace "$NAMESPACE"} --section=semodule changed_modules)"
[[ -z "$modules" ]] && modules="$changed_modules"
for module in $modules ; do
if [[ "$changed_modules" =~ $(echo "\<$module\>") ]] ; then
changed_modules="$(echo "$changed_modules" | sed -r "s/\<${module}\>//")"
__INTERNAL_ST_PUT ${NAMESPACE:+--namespace "$NAMESPACE"} --section=semodule changed_modules "$changed_modules"
local semodule="$(echo "$semodule_list" | grep -E "^${module}\s")"
if [[ -z "$semodule" ]] ; then
rlLogError "$FUNCNAME: module '$module' does not exist, skipping"
let res++
else
local state="$(echo "$semodule" | grep -iqE '\sdisabled$' && echo disabled || echo enabled)"
local saved_state="$(__INTERNAL_ST_GET ${NAMESPACE:+--namespace "$NAMESPACE"} --section=semodule $module)"
__INTERNAL_ST_PRUNE ${NAMESPACE:+--namespace "$NAMESPACE"} --section=semodule $module
if [[ "$saved_state" == "enabled" ]] ; then
if [[ "$state" == "enabled" ]]; then
rlLogInfo "$FUNCNAME: module '$module' is already enabled"
else
to_enable="$to_enable $module"
fi
elif [[ "$saved_state" == "disabled" ]] ; then
if [[ "$state" == "disabled" ]]; then
rlLogInfo "$FUNCNAME: module '$module' is already disabled"
else
to_disable="$to_disable $module"
fi
else
rlLogError "$FUNCNAME: unexpected saved state '$saved_state'"
let res++
fi
fi
else
rlLogError "$FUNCNAME: there is no state of module '$module' saved, skipping"
let res++
fi
done
to_enable="${to_enable:1}"
to_disable="${to_disable:1}"
if [[ -n "$to_enable" ]]; then
rlLog "$FUNCNAME: enabling '$to_enable' module(s), running 'semodule -e $to_enable'"
semodule -e $to_enable
local semodule_res=$?
if [[ $semodule_res -eq 0 ]] ; then
rlLog "$FUNCNAME: semodule enable passed"
else
rlLog "$FUNCNAME: semodule enable failed with exit code '$semodule_res'"
let res++
fi
fi
if [[ -n "$to_disable" ]]; then
rlLog "$FUNCNAME: disabling '$to_disable' module(s), running 'semodule -d $to_disable'"
semodule -d $to_disable
local semodule_res=$?
if [[ $semodule_res -eq 0 ]] ; then
rlLog "$FUNCNAME: semodule disable passed"
else
rlLog "$FUNCNAME: semodule disable failed with exit code '$semodule_res'"
let res++
fi
fi
else
rlLogError "$FUNCNAME: could not get modules list"
let res++
fi
return $res
}
__INTERNAL_rlSEenable_full_auditing() {
local rules_file=/etc/audit/rules.d/audit.rules
if rlIsRHEL '<7'; then
rules_file="/etc/audit/audit.rules"
fi
local final_rules=/etc/audit/audit.rules
local config_file=/etc/audit/auditd.conf
local auditd_need_restart=1
local rules="-D"$'\n'"-w /etc/shadow -p w"
local res=0
if ! diff -u <(grep -v -e '^$' -e '^#' $final_rules) <(echo "$rules") > /dev/null; then
echo "$rules" > ${rules_file} && {
auditd_need_restart=1
} || {
rlLogError "could not enable full path resolving"
let res++
}
fi
rlIsRHEL '<7' || {
if grep -q "log_format = ENRICHED" $config_file; then
rlLog "enriched audit log format already enabled"
else
rlLog "enabling enriched audit log format"
sed -r -i 's/log_format =.*/log_format = ENRICHED/' $config_file && {
auditd_need_restart=1
} || {
rlLogError "could not enable ENRICHED logging"
let res++
}
fi
}
[[ $auditd_need_restart -eq 1 ]] && {
rlLog "stop the audit daemon first"
rlRun "service auditd stop"
sleep 5
rlLog "audit daemon configuration file is updated, starting the audit service"
rlServiceStart auditd || {
rlLogError "audit daemon was not restarted correctly"
let res++
}
}
if ! diff -u <(grep -v -e '^$' -e '^#' $final_rules) <(echo "$rules") > /dev/null; then
rlLogError "$final_rules is not updated"
let res++
fi
return $res
}
__INTERNAL_rlSEcache_checksum() {
local cachefile="$__INTERNAL_rlSE_CACHEFILE"
local sumfile="$__INTERNAL_rlSE_SUMFILE"
mkdir -p "$rlSE_CACHE_DIR" || {
rlLogError "cannot create rule cache dir"
return 1
}
local currentsum=$(sha1sum /sys/fs/selinux/policy) || {
rlLogError "cannot sha1sum selinux policy in /sys"
return 1
}
if [ -s "$cachefile" -a -s "$sumfile" ]; then
oldsum=$(<"$sumfile")
if [ "$currentsum" = "$oldsum" ]; then
# checksums identical, cache still valid
return 0
fi
fi
# checksum nonexisten / different, overwrite old by current
# and trigger cache rebuild
echo "$currentsum" > "$sumfile"
return 2
}
#
# COMPATIBILITY NOTES:
# - since rlSESearchRule doesn't use -p / --perms, we don't support it here,
# but it should be easy to add if needed
# - multiple permissions of one original rule are split into multiple rows,
# each with 1 permission
# - if searching by attribute in -s / -t, only rules specifying the attribute
# are returned, not rules for all types they encompass
# - searching by type works as with setools/sesearch
# - filename in type_transition rules is always returned in double quotes,
# like setools v4.1, unlike setools v4.2+
#
__INTERNAL_rlSEcache_sesearch() {
local opts=$(getopt -o "s:t:c:" -l "allow,dontaudit,type_trans" -- "$@" \
2> >(while read -r line; do rlLogError "$FUNCNAME: $line"; done))
[ $? -ne 0 ] && return 1
eval set -- "$opts"
local in_source= in_target= in_class= ruletype=
while true; do
case "$1" in
'-s') shift; in_source="$1"; shift ;;
'-t') shift; in_target="$1"; shift ;;
'-c') shift; in_class="$1"; shift ;;
'--allow') ruletype="allow"; shift ;;
'--dontaudit') ruletype="dontaudit"; shift ;;
'--type_trans') ruletype="type_trans"; shift ;;
--) shift; break ;;
esac
done;
local cachefile="$__INTERNAL_rlSE_CACHEFILE"
if ! __INTERNAL_rlSEcache_checksum; then
rlLogInfo "(re)creating selinux-policy rule cache"
rm -f "$cachefile"
"$rlSELibraryDir/mkcache.py" "$cachefile" || {
rlLogError "cannot successfully create rule cache"
rm -f "$cachefile" # in case it is incomplete
return 1
}
else
rlLogInfo "using existing selinux-policy rule cache"
fi
case "$ruletype" in
allow|dontaudit)
local q="SELECT \"source\", \"target\", \"class\",
\"perm\", \"bool\", \"boolstate\"
FROM \"${ruletype}_lookup\"
WHERE
\"source\"='$in_source' AND \"target\"='$in_target'
AND \"class\"='$in_class' "
#[ "$in_perm" ] && q+="AND \"perm\" IN ($in_perm) "
local ret= row= source= target= class= perm= bool= bstate=
while IFS= read -r row; do
IFS='|' read -r source target class perm bool bstate <<<"$row"
case "$perm" in *\ *) perm="{ $perm }" ;; esac # more than one
ret="$ruletype $source $target:$class $perm;"
if [ "$bool" ]; then
ret+=" [ $bool ]:"
[ "$bstate" = "1" ] && ret+="True" || ret+="False"
fi
echo "$ret"
done < <(echo "$q" | sqlite3 "$cachefile")
;;
type_trans)
local q="SELECT \"source\", \"target\", \"class\", \"default\",
\"filename\", \"bool\", \"boolstate\"
FROM \"${ruletype}_lookup\"
WHERE
\"source\"='$in_source' AND \"target\"='$in_target'
AND \"class\"='$in_class' "
local ret= row= source= target= class= default= filename= bool= bstate=
while IFS= read -r row; do
IFS='|' read -r source target class default filename bool bstate <<<"$row"
ret="type_transition $source $target:$class $default"
[ "$filename" ] && ret+=" \"$filename\""
ret+=';'
if [ "$bool" ]; then
ret+=" [ $bool ]:"
[ "$bstate" = "1" ] && ret+="True" || ret+="False"
fi
echo "$ret"
done < <(echo "$q" | sqlite3 "$cachefile")
;;
*)
rlLogError "unsupported cache rule type: $ruletype"
return 1
;;
esac
}
rlSELibraryLoaded() {
__INTERNAL_SEMODULE_LISTING="semodule -lfull"
local tmp
eval tmp="\$($__INTERNAL_SEMODULE_LISTING 2>&1 )"
if [[ "$tmp" =~ invalid\ option ]]; then
__INTERNAL_SEMODULE_LISTING='semodule -l'
fi
rlLogInfo "SELinux: using '$__INTERNAL_SEMODULE_LISTING' to list modules"
__INTERNAL_POLICY_NAME="$(sestatus | grep -i 'Loaded policy name' | sed -r 's/.*:\s*([^:]+)\s*/\1/')"
__INTERNAL_POLICY_NAME="${__INTERNAL_POLICY_NAME:-$(sestatus | grep -i 'Policy from config file' | sed -r 's/.*:\s*([^:]+)\s*/\1/')}"
__INTERNAL_POLICY_NAME="${__INTERNAL_POLICY_NAME:-targeted}"
__INTERNAL_POLICY_ROOT="$(sestatus | grep -i 'SELinux root directory' | sed -r 's/.*:\s*([^:]+)\s*/\1/')"
__INTERNAL_POLICY_ROOT="${__INTERNAL_POLICY_ROOT:-/etc/selinux}"
__INTERNAL_POLICY_PATH="$__INTERNAL_POLICY_ROOT/$__INTERNAL_POLICY_NAME"
__INTERNAL_POLICY_FILE="$__INTERNAL_POLICY_PATH/policy/policy.$(ls -1 -d $__INTERNAL_POLICY_PATH/policy/policy.* | sed -r 's/[^.]*\.//' | sort -nr | head -n 1)"
__INTERNAL_POLICY_STORE_ROOT="/var/lib/selinux"
rlIsRHEL '<8' && __INTERNAL_POLICY_STORE_ROOT="/etc/selinux"
export rlSEpolicyRoot="$__INTERNAL_POLICY_ROOT"
export rlSEstoreRoot="$__INTERNAL_POLICY_STORE_ROOT"
export rlSEpolicyName="$__INTERNAL_POLICY_NAME"
rlLogInfo "Running with policy located in $__INTERNAL_POLICY_FILE"
if rlIsRHEL 4 5; then
rlSE_REQUIRES="setools expect policycoreutils"
elif rlIsRHEL 6 ; then
rlSE_REQUIRES="setools-console expect policycoreutils-python"
elif rlIsRHEL 7 ; then
rlSE_REQUIRES="setools-console expect policycoreutils-python selinux-policy-devel"
else
rlSE_REQUIRES="setools-console expect policycoreutils-python-utils selinux-policy-devel"
fi
__INTERNAL_rlSEenable_full_auditing
local t=$(date +%s)
rlLogInfo "SELinux related packages listing:"
rlLogInfo "$(rpm -qa | grep -e ^selinux-policy -e ^libsemanage -e ^policycoreutils -e ^setools -e ^libselinux -e ^libsepol -e ^checkpolicy -e ^mcstrans -e ^setroubleshoot | sort | sed 's/^/ /')"
rlLogInfo " listing took $(($(date +%s)-$t)) second(s)"
if rlCheckRequirements $rlSE_REQUIRES; then
SETOOLS_MAJOR_VERSION=`sesearch --version 2>&1| sed -n 's/sesearch //;s/\..*$//p'`
rlLogDebug "FUNCNAME(): sesearch --version: $(sesearch --version 2>&1)"
rlLogDebug "FUNCNAME(): SETOOLS_MAJOR_VERSION=$SETOOLS_MAJOR_VERSION"
rlLogDebug "rlSE Library: Requires installed."
return 0
fi
if rlIsRHEL 7 ; then
if uname -r | grep -q x86_64 ; then
local LIBSEPOL_VER_REL=`rpm -q --queryformat "%{version},%{release}" libsepol`
local LIBSELINUX_VER_REL=`rpm -q --queryformat "%{version},%{release}" libselinux`
local SETOOLS_VER_REL=`rpm -q --queryformat "%{version},%{release}" setools-libs`
# remove all i686 setools* packages
rlRun "yum -y remove setools\*.i686"
# install necessary packages
rlRun "yum -y install setools-libs setools-console libsepol libselinux libselinux-python libselinux-utils -x \*.i686" 0,1
fi
fi
if rlIsRHEL; then
rlRun "yum -y --skip-broken install $rlSE_REQUIRES"
else
rlRun "dnf -y --skip-broken install $rlSE_REQUIRES"
fi
if rlCheckRequirements $rlSE_REQUIRES; then
SETOOLS_MAJOR_VERSION=`sesearch --version 2>&1| sed -n 's/sesearch //;s/\..*$//p'`
rlLogDebug "FUNCNAME(): sesearch --version: $(sesearch --version 2>&1)"
rlLogDebug "FUNCNAME(): SETOOLS_MAJOR_VERSION=$SETOOLS_MAJOR_VERSION"
rlLogDebug "rlSE Library: Requires installed."
return 0
fi
rlLogError "rlSE Library: Not all required packages installed."
return 1
}
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Authors
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
: <<'=cut'
=pod
=head1 AUTHORS
=over
=item *
Milos Malik <mmalik@redhat.com>
Michal Trunecka <mtruneck@redhat.com>
David Spurek <dspurek@redhat.com>
=back
=cut