#!/bin/bash # test01_pkgquality -- @package@ test (rpm quality checks) # Copyright (C) 2008,2012,2013 Davide Madrisan [ -z "$BASH" ] || [ ${BASH_VERSION:0:1} -lt 2 ] && echo $"this script requires bash version 2 or better" >&2 && exit 1 me=("test01_pkgquality" "@version@" "@date@") [ -r @libdir@/libmsgmng.lib ] || { echo "$me: "$"library not found"": @libdir@/libmsgmng.lib" 1>&2 exit 1; } . @libdir@/libmsgmng.lib [ -r @libdir@/libtranslate.lib ] || { echo "$me: "$"library not found"": @libdir@/libtranslate.lib" 1>&2 exit 1; } . @libdir@/libtranslate.lib notify.debug $"loading"": \`test01_pkgquality'..." # check if all the needed tools are available for tool in file find getopt grep ls ldd wc; do [ "$(type -p $tool)" ] || notify.error $"utility not found"": \`$tool'" done function alltests() { notify.note " ${NOTE}"$"performing quality checks""${NORM}""..." TEMP=`LC_ALL=C getopt \ -o i:t: --long infofile:,tmpdir: \ -n "$FUNCNAME" -- "$@"` [ $? = 0 ] || return 1 eval set -- "$TEMP" while :; do case "$1" in -i|--infofile) rpminfofile="$2" shift ;; -t|--tmpdir) tmpextractdir="$2" shift ;; --) shift; break ;; *) notify.error $"\ (bug)"" -- $FUNCNAME: "$"\`getopt' error" ;; esac shift done [ "$rpminfofile" ] || notify.error $"\ (bug)"" -- $FUNCNAME: "$"missing mandatory arg"" (--infofile)" [ -r "$rpminfofile" ] || notify.error $"\ (bug)"" -- $FUNCNAME: "$"cannot read"" \`$rpminfofile'" . $rpminfofile [ "$tmpextractdir" ] || notify.error $"\ (bug)"" -- $FUNCNAME: "$"missing mandatory arg"" (--tmpdir)" [ -d "$tmpextractdir" ] || notify.error $"\ (bug)"" -- $FUNCNAME: "$"no such file or directory"" \`$tmpextractdir'" local total_issues=0 # check for broken symlinks # - symlinks to files in the buildroot directory for rpm # (usable for a symlink attacks) # - symlinks not pointing to existing files test.skip $test_number || { notify.note "$(test.num2str). ${NOTE}"\ $"checking for wrong symbolic links""${NORM}..." # local rpmbuildroot=`sed -n "/%description/q;{ # /^BuildRoot[ ]*:/{s/[^ ]*[ ]*//;p}}" \ # $spec_dir/$SRPM_SPECFILE | \ # sed "s,%[{]*name[}]*,$SPEC_NAME, # s,%[{]*version[}]*,$SPEC_VERSION, # s,%[{]*_tmppath[}]*,$tmppath_dir,;p"` # FIXME: 'tmppath_dir' should be get from configuration files tmppath_dir=`rpm --eval %_tmppath 2>/dev/null` [ "$tmppath_dir" ] || notify.error $"(bug)"" -- $FUNCNAME: ""empty string"" (tmppath_dir)" notify.debug "tmppath_dir = $tmppath_dir" let "i = 0" for pck in ${rpmpkg_name[@]}; do pushd $tmpextractdir/$i >/dev/null for f in $(find -mindepth 1 -type l); do notify.debug "$f --> `readlink $f`" # FIXME: the check fails if 'BuildRoot' doesn't start # by '%_{tmppath}' # note: the first condition check for wrong links, like # /usr/share/man/man1/zcmp.1.gz -> .gz # made by the broken `brp-compress' script in rpm 4.0.4 if [[ "$(readlink $f)" = ".gz" || \ "$(readlink $f)" =~ $tmppath_dir ]]; then notify.warning "${NOTE}${pck##*/}${NORM}" notify.note $"\ wrong symlink"": \`${NOTE}${f/./}${NORM}' --> \`${NOTE}$(readlink $f)${NORM}'" let "total_issues += 1" fi done popd >/dev/null let "i += 1" done; } test_number=$(($test_number + 1)) # check for `%buildroot' strings test.skip $test_number || { if [ "$rpm_ignores_buildroot" = 1 ]; then if [ "$SPEC_BUILDROOT" ]; then notify.note \ "$(test.num2str). ${NOTE}"\ $"checking for \`$SPEC_BUILDROOT' (%buildroot) strings"\ "${NORM}... "$"skipped" else notify.note \ "$(test.num2str). ${NOTE}"\ $"checking for %buildroot strings""${NORM}... "$"N/A" fi else notify.note "$(test.num2str). "\ $"checking for \`$SPEC_BUILDROOT' (%buildroot) strings" [ "$SPEC_BUILDROOT" ] || notify.error \ $"(bug)"" -- $FUNCNAME: ""empty string"" (SPEC_BUILDROOT)" let "i = 0" for pck in ${rpmpkg_name[@]}; do pushd $tmpextractdir/$i >/dev/null for f in $(find -type f \ -exec grep -ls "$SPEC_BUILDROOT" {} \;2>/dev/null); do notify.warning "${NOTE}${pck##*/}${NORM}" notify.note "\ ${NOTE}$(echo $f | sed "s,$tmpextractdir/$i,," )${NORM}" notify.note "\ "$"mime type: ""${NOTE}$(file --brief --mime --uncompress $f)${NORM}" notify.note "$(\ strings -a $f | grep "^$SPEC_BUILDROOT" | sort -bu | \ sed "s,$SPEC_BUILDROOT\(.*\), - [%buildroot]\1,")" let "total_issues += 1" done popd >/dev/null let "i += 1" done fi; } test_number=$(($test_number + 1)) # check for `%_builddir' strings BUILDDIR="$(rpm --eval=%_builddir 2>/dev/null)" test.skip $test_number || { notify.note "$(test.num2str). ${NOTE}"\ $"checking for \`$BUILDDIR' (%_builddir) strings""${NORM}... " [ "$BUILDDIR" ] || notify.error $"(bug)"" -- $FUNCNAME: ""empty string"" (BUILDDIR)" let "i = 0" for pck in ${rpmpkg_name[@]}; do pushd $tmpextractdir/$i >/dev/null for f in $(find -type f \ -exec grep -ls "$BUILDDIR" {} \; 2>/dev/null); do notify.warning "${NOTE}${pck##*/}${NORM}" notify.note "\ ${NOTE}$(echo $f | sed "s,$tmpextractdir/$i,," )${NORM}" notify.note "\ "$"mime type: ""${NOTE}$(file --brief --mime --uncompress $f)${NORM}" notify.note "$(\ strings -a $f | grep "$BUILDDIR" | sort -bu | \ sed "s,$BUILDDIR,[%_builddir],g;s,.*, - &,")" let "total_issues += 1" done popd >/dev/null let "i += 1" done; } test_number=$(($test_number + 1)) # check for suspected plugins (.la, .so) in devel packages # note: pure plugins must be in the main package, not in devel test.skip $test_number || { notify.note "$(test.num2str). ${NOTE}"\ $"checking for suspicious plugins in devel packages""${NORM}..." let "i = 0" for pck in ${rpmpkg_name[@]}; do # skip non devel packages [[ "${pck##*/}" =~ -devel- ]] || { let "i += 1"; continue; } pushd $tmpextractdir/$i >/dev/null # find *.so files that are not symlinks to dynamic libraries for f in `\ find -mindepth 1 -type f -name \*.so -exec file {} \; | \ grep ' shared object,' | sed -n 's/.\(.*\):.*/\1/p'`; do notify.warning "${NOTE}${pck##*/}${NORM}" notify.note $"found suspect plugin \`${NOTE}$f${NORM}'" let "total_issues += 1" done popd >/dev/null let "i += 1" done; } test_number=$(($test_number + 1)) # check for wrong file attributes in lib and bin dirs test.skip $test_number || { notify.note "$(test.num2str). ${NOTE}"\ $"checking for wrong file attributes in bin and lib directories""${NORM}..." warning=0 let "i = 0" for pck in ${rpmpkg_name[@]}; do pushd $tmpextractdir/$i >/dev/null for f in $( find . -type f \( \ \( -name '*.so.*' -and -not -name '*.so.owner' \ -and -not -perm 755 \) -or \ \( -name '*.so' -and -not -perm 755 \) -or \ \( \( -path './bin/*' -or \ -path './sbin/*' -or \ -path './usr/bin/*' -or \ -path './usr/sbin/*' \) \ -not -perm -111 \) 2>/dev/null \) ); do let "warning = 1" && notify.warning "${NOTE}${pck##*/}${NORM}" notify.note $"found suspect file"": \ \`${NOTE}${f/./}${NORM}' [$(ls -l "$f" | sed 's, .*,,')]" let "total_issues += 1" done let "i += 1" popd >/dev/null done [[ $warning -eq 0 ]] || notify.note "\ ----------------------------- ${NOTE}"$"Hint"":${NORM} # fixup strange shared library permissions chmod 755 %{buildroot}%{_libdir}/*.so* %files %defattr(-,root,root) ... %attr(0755,root,root) %{_bindir}/ -----------------------------"; } test_number=$(($test_number + 1)) # check for libraries with undefined symbols test.skip $test_number || { notify.note "$(test.num2str). ${NOTE}"\ $"checking for libraries with undefined symbols after relocation""${NORM}..." let "i = 0" local undefsyms for pck in ${rpmpkg_name[@]}; do # skip debug packages [[ "${pck##*/}" =~ -debug- ]] && { let "i += 1"; continue; } pushd $tmpextractdir/$i >/dev/null for f in `find -mindepth 1 -type f \ \( -name *\.so\.* -not -name *.debug \) -exec file {} \; | \ grep 'shared object' | sed -n 's/.\([^:]*\):.*/\1/p'`; do undefsyms="\ $(LC_ALL=C ldd -d -r "$f" 2>/dev/null |& grep "undefined symbol")" if [ "$undefsyms" ]; then notify.warning "${NOTE}${pck##*/}${NORM}" echo -n "${CRIT}" LC_ALL=C ldd -d -r "$f" 2>&1 | \ grep "undefined symbol" | sed "s,.*, - &," echo -n "${NORM}" let "total_issues += 1" fi done popd >/dev/null let "i += 1" done; } test_number=$(($test_number + 1)) # check for binary files in etc (see FHS-2.2) test.skip $test_number || { notify.note "$(test.num2str). ${NOTE}"\ $"checking for binary files installed in /etc (see FHS)""${NORM}..." warning=0 let "i = 0" for pck in ${rpmpkg_name[@]}; do pushd $tmpextractdir/$i >/dev/null for f in $( find ./etc -type f -perm +111 2>/dev/null ); do case $f in ./etc/rc.d/init.d/*) ;; *) let "warning = 1" && { notify.warning "${NOTE}${pck##*/}${NORM}" notify.note $"found suspect file"": \ \`${NOTE}${f/./}${NORM}' [$(ls -l "$f" | sed 's, .*,,')]" let "total_issues += 1"; } ;; esac done popd >/dev/null let "i += 1" done [ "$warning" = 0 ] || notify.note "\ ----------------------------- ${NOTE}"$"Hint"":${NORM} %files %defattr(-,root,root) ... %attr(0644,root,root) %{_sysconfdir}/<...file> -----------------------------"; } #|| exit 1 test_number=$(($test_number + 1)) # check for installation code needed by info pages test.skip $test_number || { notify.note "$(test.num2str). ${NOTE}"\ $"checking if the info catalog is updated when necessary""${NORM}..." error=0 let "i = 0" for pck in ${rpmpkg_name[@]}; do [[ -e $pck ]] || notify.error $"package not found"": \`${pck##*/}'" [[ "$(rpm -p -ql $pck | # FIXME: this check only works for FHS-compliant distros grep "^$(rpm --eval %_infodir)")" ]] || { let "i += 1"; continue; } # no info pages found #notify.debug "$FUNCNAME: info page(s) found" [[ "$(rpm -p -q --requires $pck 2>/dev/null | sed -n ' /^[ \t]*\/sbin\/install-info/p')" ]] || let "error+=1" # note: we just check for at list two occurences of the command # '/sbin/install-info' in the package scriptlets [[ "$(rpm -p -q --scripts $pck 2>/dev/null | sed -n ' /^[ \t]*\/sbin\/install-info/p' | wc -l)" -ge 2 ]] || let "error+=1" [ "$error" = "0" ] || { notify.warning "${NOTE}${pck##*/}${NORM}" notify.note $"info pages should be installed/uninstalled""${NORM} --------------------------------------- ${NOTE}"$"Hint"":${NORM} %package [] ... $(if [[ "$rpm_macro_installinfo_binary" ]]; then echo "\ Requires(post):$rpm_macro_installinfo_binary Requires(preun):$rpm_macro_installinfo_binary" else echo "\ Requires(post):${path_installinfo:-/sbin/install-info} Requires(preun):${path_installinfo:-/sbin/install-info}" fi) %post [] $([[ "$rpm_macro_installinfo" ]] && echo "$rpm_macro_installinfo %{name}.info" || echo "${path_installinfo:-/sbin/install-info} %{name}.info") exit 0 %preun [] $([[ "$rpm_macro_uninstallinfo" ]] && echo "$rpm_macro_uninstallinfo %{name}.info" || echo "${path_installinfo:-/sbin/install-info} --delete %{name}.info") exit 0 ---------------------------------------" let "total_issues += $error"; } done; } test_number=$(($test_number + 1)) # check packages for wrong user and/or group ownerships test.skip $test_number || { notify.note "$(test.num2str). ${NOTE}"\ $"checking packages for wrong user and/or group ownerships""${NORM}..." error=0 idun="$(id -un)" idgn="$(id -gn)" let "i = 0" for pck in ${rpmpkg_name[@]}; do [[ -e $pck ]] || notify.error $"\ package not found"": \`${pck##*/}'" ( LC_ALL=C rpm -p -qlv $pck | \ while read line; do set -- $line # FIXME : find a better check, perhaps using a range # of uid reserved for users if [[ "$idun" = "$3" || "$idgn" = "$4" ]]; then notify.warning "${NOTE}${pck##*/}${NORM}" notify.note $"found suspect file"": \ \`${NOTE}$9${NORM}' [uid:\`${NOTE}$3${NORM}', gid:\`${NOTE}$4${NORM}']" let "total_issues += 1" fi done ) done; } test_number=$(($test_number + 1)) # check for desktop files installed in non standard applnk dir test.skip $test_number || { notify.note "$(test.num2str). ${NOTE}"\ $"checking packages for desktop files installed in the applnk dir""${NORM}..." warning=0 rpmdatadir=$(rpm --eval %_datadir 2>/dev/null) let "i = 0" for pck in ${rpmpkg_name[@]}; do pushd $tmpextractdir/$i >/dev/null for f in $( find .${rpmdatadir} -type f 2>/dev/null ); do case $f in .${rpmdatadir}/applnk/*.desktop) let "warning = 1" && { notify.warning "${NOTE}${pck##*/}${NORM}" notify.note $"found suspect file"": \ \`${NOTE}${f/./}${NORM}' [$(ls -l "$f" | sed 's, .*,,')]" let "total_issues += 1"; } ;; *) ;; esac done popd >/dev/null done [ "$warning" = 0 ] || notify.note "\ ----------------------------- ${NOTE}"$"Hint"":${NORM} "$"create desktop files for:"" ${rpmdatadir}/applications "$"see:"" -----------------------------"; } test_number=$(($test_number + 1)) # check if a package that do not contains binaries is tagged noarch test.skip $test_number || { notify.note "$(test.num2str). ${NOTE}"\ $"checking for packages with bad BuildArch tag""${NORM}..." warning=0 let "i = 0" for pck in ${rpmpkg_name[@]}; do pushd $tmpextractdir/$i >/dev/null for f in $(LC_ALL=C find -mindepth 2 -type f \ -exec file {} \; 2>/dev/null | grep -E "(\ ELF | OCaml library | ar archive |\ dynamically linked | statically linked )"); do notify.debug "found binary or library file: \`${NOTE}${f/./}${NORM}'" let "warning = 1" break done popd >/dev/null done if [ "$warning" = 0 ]; then [ "$SPEC_BUILDARCH" = "noarch" ] || { notify.warning "${NOTE}${pck##*/}${NORM}" notify.note $"this package should be tagged \`noarch'"" ----------------------------- ${NOTE}"$"Hint"":${NORM} BuildArch: noarch -----------------------------" let "total_issues += 1"; } fi; } test_number=$(($test_number + 1)) notify.note " --> "$"${NOTE}Quality checks: ${#rpmpkg_name[@]} \ package(s) checked: ${NORM}${WARN}$total_issues${NORM}${NOTE} warning(s).${NORM}"" " }