rpmlint/FilesCheck.py

715 lines
25 KiB
Python

#############################################################################
# File : FilesCheck.py
# Package : rpmlint
# Author : Frederic Lepied
# Created on : Mon Oct 4 19:32:49 1999
# Version : $Id$
# Purpose : test various aspects on files: locations, owner, groups,
# permission, setuid, setgid...
#############################################################################
from Filter import *
import AbstractCheck
import rpm
import re
import stat
import string
import os
# must be kept in sync with the filesystem package
STANDARD_DIRS=(
'/bin',
'/boot',
'/etc',
'/etc/X11',
'/etc/opt',
'/etc/skel',
'/etc/xinetd.d',
'/home',
'/lib',
'/lib64',
'/lib/modules',
'/mnt',
'/mnt/cdrom',
'/mnt/disk',
'/mnt/floppy',
'/opt',
'/proc',
'/root',
'/sbin',
'/tmp',
'/usr',
'/usr/X11R6',
'/usr/X11R6/bin',
'/usr/X11R6/doc',
'/usr/X11R6/include',
'/usr/X11R6/lib',
'/usr/X11R6/lib64',
'/usr/X11R6/man',
'/usr/X11R6/man/man1',
'/usr/X11R6/man/man2',
'/usr/X11R6/man/man3',
'/usr/X11R6/man/man4',
'/usr/X11R6/man/man5',
'/usr/X11R6/man/man6',
'/usr/X11R6/man/man7',
'/usr/X11R6/man/man8',
'/usr/X11R6/man/man9',
'/usr/X11R6/man/mann',
'/usr/bin',
'/usr/bin/X11',
'/usr/etc',
'/usr/games',
'/usr/include',
'/usr/lib',
'/usr/lib64',
'/usr/lib/X11',
'/usr/lib/games',
'/usr/lib/gcc-lib',
'/usr/lib64/gcc-lib',
'/usr/local',
'/usr/local/bin',
'/usr/local/doc',
'/usr/local/etc',
'/usr/local/games',
'/usr/local/info',
'/usr/local/lib',
'/usr/local/lib64',
'/usr/local/man',
'/usr/local/man/man1',
'/usr/local/man/man2',
'/usr/local/man/man3',
'/usr/local/man/man4',
'/usr/local/man/man5',
'/usr/local/man/man6',
'/usr/local/man/man7',
'/usr/local/man/man8',
'/usr/local/man/man9',
'/usr/local/man/mann',
'/usr/local/sbin',
'/usr/local/src',
'/usr/sbin',
'/usr/share',
'/usr/share/dict',
'/usr/share/icons',
'/usr/share/doc',
'/usr/share/info',
'/usr/share/man',
'/usr/share/man/man1',
'/usr/share/man/man2',
'/usr/share/man/man3',
'/usr/share/man/man4',
'/usr/share/man/man5',
'/usr/share/man/man6',
'/usr/share/man/man7',
'/usr/share/man/man8',
'/usr/share/man/man9',
'/usr/share/man/mann',
'/usr/share/misc',
'/usr/src',
'/usr/tmp',
'/var',
'/var/cache',
'/var/db',
'/var/lib',
'/var/lib/games',
'/var/lib/misc',
'/var/lib/rpm',
'/var/local',
'/var/lock',
'/var/lock/subsys',
'/var/log',
'/var/mail',
'/var/nis',
'/var/opt',
'/var/preserve',
'/var/run',
'/var/spool',
'/var/spool/mail',
'/var/tmp',
'/etc/profile.d'
)
DEFAULT_GAMES_GROUPS='Games'
DEFAULT_DANGLING_EXCEPTIONS = (['consolehelper$', 'usermode'],
)
tmp_regex=re.compile('^/tmp/|^(/var|/usr)/tmp/')
mnt_regex=re.compile('^/mnt/')
opt_regex=re.compile('^/opt/')
home_regex=re.compile('^/home/')
etc_regex=re.compile('^/etc/')
sub_bin_regex=re.compile('^(/usr)?/s?bin/\S+/')
backup_regex=re.compile('~$|\#[^/]+\#$')
compr_regex=re.compile('\.(gz|z|Z|zip|bz2)$')
absolute_regex=re.compile('^/([^/]+)')
absolute2_regex=re.compile('^/?([^/]+)')
points_regex=re.compile('^../(.*)')
doc_regex=re.compile('^/usr/(doc|man|info)|^/usr/share/(doc|man|info)')
bin_regex=re.compile('^(/usr)?/s?bin/')
includefile_regex=re.compile('\.[ch]$|\.a$')
sofile_regex=re.compile('/lib(64)?/[^/]+\.so$')
devel_regex=re.compile('-(devel|source)$')
lib_regex=re.compile('lib(64)?/lib[^/]*\.so\..*')
ldconfig_regex=re.compile('^[^#]*ldconfig', re.MULTILINE)
depmod_regex=re.compile('^[^#]*depmod', re.MULTILINE)
info_regex=re.compile('^/usr/share/info')
install_info_regex=re.compile('^[^#]*install-info', re.MULTILINE)
perl_temp_file=re.compile('.*perl.*(bs|\.packlist)$')
cvs_regex=re.compile('/CVS/[^/]+$|/.cvsignore$')
games_path_regex=re.compile('/usr/(lib/)?/games')
games_group_regex=re.compile(Config.getOption('RpmGamesGroups', DEFAULT_GAMES_GROUPS))
source_regex=re.compile('(.c|.cc|.cpp|.ui)$')
dangling_exceptions=Config.getOption('DanglingSymlinkExceptions', DEFAULT_DANGLING_EXCEPTIONS)
logrotate_regex=re.compile('^/etc/logrotate.d/(.*)')
module_rpms_ok=Config.getOption('KernelModuleRPMsOK', 1)
kernel_modules_regex=re.compile('^/lib/modules/(2.[23456].[0-9]+[^/]*?)/')
kernel_package_regex=re.compile('^kernel(22)?(-)?(smp|enterprise|bigmem|secure|BOOT|i686-up-4GB|p3-smp-64GB)?')
normal_zero_length_regex=re.compile('^/etc/security/console.apps/|/.nosearch$|/__init__.py$')
perl_regex=re.compile('^/usr/lib/perl5/(?:site_perl/)?([0-9]+\.[0-9]+)\.([0-9]+)/')
python_regex=re.compile('^/usr/lib/python([.0-9]+)/')
cross_compile_regex=re.compile('-mandrake-linux-[^/]+$')
perl_version_trick=Config.getOption('PerlVersionTrick', 1)
log_regex=re.compile('^/var/log/[^/]+$')
lib_path_regex=re.compile('^(/usr(/X11R6)?)?/lib(64)?')
lib_package_regex=re.compile('^(lib|.+-libs)')
hidden_file_regex=re.compile('/\.[^/]*$')
for idx in range(0, len(dangling_exceptions)):
dangling_exceptions[idx][0]=re.compile(dangling_exceptions[idx][0])
class FilesCheck(AbstractCheck.AbstractCheck):
def __init__(self):
AbstractCheck.AbstractCheck.__init__(self, 'FilesCheck')
def check(self, pkg):
# Check only binary package
if pkg.isSource():
return
# Check if the package is a development package
devel_pkg=devel_regex.search(pkg.name)
files=pkg.files()
config_files=pkg.configFiles()
ghost_files=pkg.ghostFiles()
doc_files=pkg.docFiles()
req_names=pkg.req_names()
deps=pkg.requires()+pkg.prereq()
prein=pkg[rpm.RPMTAG_PREIN]
lib_package=lib_package_regex.search(pkg.name)
is_kernel_package=kernel_package_regex.search(pkg.name)
# erport these errors only once
perl_dep_error=0
python_dep_error=0
lib_file=0
non_lib_file=0
if doc_files == [] and not (pkg.name[:3] == 'lib' and string.find(pkg.name, '-devel')):
printWarning(pkg, 'no-documentation')
for f in files.keys():
enreg=files[f]
mode=enreg[0]
user=enreg[1]
group=enreg[2]
size=enreg[4]
if not user in Config.STANDARD_USERS:
printError(pkg, 'non-standard-uid', f, user)
if not group in Config.STANDARD_GROUPS:
printError(pkg, 'non-standard-gid', f, group)
if not module_rpms_ok and kernel_modules_regex.search(f) and not is_kernel_package:
printError(pkg, "kernel-modules-not-in-kernel-packages", f)
if tmp_regex.search(f):
printError(pkg, 'dir-or-file-in-tmp', f)
elif mnt_regex.search(f):
printError(pkg, 'dir-or-file-in-mnt', f)
elif opt_regex.search(f):
printError(pkg, 'dir-or-file-in-opt', f)
elif sub_bin_regex.search(f):
printError(pkg, 'subdir-in-bin', f)
elif backup_regex.search(f):
printError(pkg, 'backup-file-in-package', f)
elif home_regex.search(f):
printError(pkg, 'dir-or-file-in-home', f)
elif cvs_regex.search(f):
printError(pkg, 'cvs-internal-file', f)
elif hidden_file_regex.search(f):
printWarning(pkg, 'hidden-file-or-dir', f)
elif f == '/usr/info/dir' or f == '/usr/share/info/dir':
printError(pkg, 'info-dir-file', f)
res=logrotate_regex.search(f)
if res and res.group(1) != pkg.name:
printError(pkg, 'incoherent-logrotate-file', f)
if etc_regex.search(f) and stat.S_ISREG(mode):
if not f in config_files and not f in ghost_files:
printWarning(pkg, 'non-conffile-in-etc', f)
link=enreg[3]
if link != '':
ext=compr_regex.search(link)
if ext:
if not re.compile('\.' + ext.group(1) + '$').search(f):
printError(pkg, 'compressed-symlink-with-wrong-ext', f, link)
perm=mode & 07777
# bit s check
if stat.S_ISGID & mode or stat.S_ISUID & mode:
# check only normal files
if stat.S_ISREG(mode):
user=enreg[1]
group=enreg[2]
setuid=None
setgid=None
if stat.S_ISUID & mode:
setuid=user
if stat.S_ISGID & mode:
setgid=group
if setuid and setgid:
printError(pkg, 'setuid-gid-binary', f, setuid, setgid, oct(perm))
elif setuid:
printError(pkg, 'setuid-binary', f, setuid, oct(perm))
elif setgid:
if not (group == 'games' and
(games_path_regex.search(f) or games_group_regex.search(pkg[rpm.RPMTAG_GROUP]))):
printError(pkg, 'setgid-binary', f, setgid, oct(perm))
elif mode & 0777 != 0755:
printError(pkg, 'non-standard-executable-perm', f, oct(perm))
# normal file check
if stat.S_ISREG(mode):
if not devel_pkg:
if lib_path_regex.search(f):
lib_file=1
elif f not in doc_files:
non_lib_file=f
if log_regex.search(f):
if user != 'root':
printError(pkg, 'non-root-user-log-file', f, user)
if group != 'root':
printError(pkg, 'non-root-group-log-file', f, group)
if not f in ghost_files:
printError(pkg, 'non-ghost-file', f)
if doc_regex.search(f) and not f in doc_files:
printError(pkg, 'not-listed-as-documentation', f)
#elif cross_compile_regex.search(f):
# printError(pkg, 'cross-compile-name', f)
# check ldconfig call in %post and %postun
if lib_regex.search(f):
postin=pkg[rpm.RPMTAG_POSTIN] or pkg[rpm.RPMTAG_POSTINPROG]
if not postin:
printError(pkg, 'library-without-ldconfig-postin', f)
else:
if not ldconfig_regex.search(postin):
printError(pkg, 'postin-without-ldconfig', f)
postun=pkg[rpm.RPMTAG_POSTUN] or pkg[rpm.RPMTAG_POSTUNPROG]
if not postun:
printError(pkg, 'library-without-ldconfig-postun', f)
else:
if not ldconfig_regex.search(postun):
printError(pkg, 'postun-without-ldconfig', f)
# check depmod call in %post and %postun
res=not is_kernel_package and kernel_modules_regex.search(f)
if res:
kernel_version=res.group(1)
kernel_version_regex=re.compile('depmod -a.*-F /boot/System.map-' + kernel_version + '.*' + kernel_version, re.MULTILINE | re.DOTALL)
postin=pkg[rpm.RPMTAG_POSTIN] or pkg[rpm.RPMTAG_POSTINPROG]
if not postin or not depmod_regex.search(postin):
printError(pkg, 'module-without-depmod-postin', f)
# check that we run depmod on the right kernel
else:
if not kernel_version_regex.search(postin):
printError(pkg, 'postin-with-wrong-depmod', f)
postun=pkg[rpm.RPMTAG_POSTUN] or pkg[rpm.RPMTAG_POSTUNPROG]
if not postun or not depmod_regex.search(postun):
printError(pkg, 'module-without-depmod-postun', f)
# check that we run depmod on the right kernel
else:
if not kernel_version_regex.search(postun):
printError(pkg, 'postun-with-wrong-depmod', f)
# check install-info call in %post and %postun
if info_regex.search(f):
postin=pkg[rpm.RPMTAG_POSTIN]
if not postin:
printError(pkg, 'info-files-without-install-info-postin', f)
else:
if not install_info_regex.search(postin):
printError(pkg, 'postin-without-install-info', f)
postun=pkg[rpm.RPMTAG_POSTUN]
preun=pkg[rpm.RPMTAG_PREUN]
if not postun and not preun:
printError(pkg, 'info-files-without-install-info-postun', f)
else:
if (not postun or not install_info_regex.search(postun)) and \
(not preun or not install_info_regex.search(preun)):
printError(pkg, 'postin-without-install-info', f)
# check perl temp file
if perl_temp_file.search(f):
printWarning(pkg, 'perl-temp-file', f)
if bin_regex.search(f) and mode & 0111 == 0:
printWarning(pkg, 'non-executable-in-bin', f, oct(perm))
if not devel_pkg and includefile_regex.search(f) and not f in doc_files:
printWarning(pkg, 'devel-file-in-non-devel-package', f)
if mode & 0444 != 0444 and perm & 07000 == 0 and f[0:len('/var/log')] != '/var/log':
printError(pkg, 'non-readable', f, oct(perm))
if size == 0 and not normal_zero_length_regex.search(f) and f not in ghost_files:
printError(pkg, 'zero-length', f)
if not perl_dep_error:
res=perl_regex.search(f)
if res:
if perl_version_trick:
vers = res.group(1) + '0' + res.group(2)
else:
vers = res.group(1) + res.group(2)
if not (pkg.check_versioned_dep('perl-base', vers) or
pkg.check_versioned_dep('perl', vers)):
printError(pkg, 'no-dependancy', 'perl-base', vers)
perl_dep_error=1
if not python_dep_error:
res=python_regex.search(f)
if res:
if not (pkg.check_versioned_dep('python-base', res.group(1)) or
pkg.check_versioned_dep('python', res.group(1))):
printError(pkg, 'no-dependancy', 'python-base', res.group(1))
python_dep_error=1
# normal executable check
if mode & stat.S_IXUSR and perm != 0755:
printError(pkg, 'non-standard-executable-perm', f, oct(perm))
# normal dir check
elif stat.S_ISDIR(mode):
if perm != 0755:
printError(pkg, 'non-standard-dir-perm', f, oct(perm))
if pkg[rpm.RPMTAG_NAME] != 'filesystem':
if f in STANDARD_DIRS:
printError(pkg, 'standard-dir-owned-by-package', f)
if hidden_file_regex.search(f):
printWarning(pkg, 'hidden-file-or-dir', f)
# symbolic link check
elif stat.S_ISLNK(mode):
r=absolute_regex.search(link)
is_so=sofile_regex.search(f)
if not devel_pkg and is_so:
printWarning(pkg, 'devel-file-in-non-devel-package', f)
# absolute link
if r:
if (not is_so) and link not in files.keys():
is_exception=0
for e in dangling_exceptions:
if e[0].search(link):
is_exception=e[1]
break
if is_exception:
if is_exception not in req_names:
printWarning(pkg, 'no-dependancy-on', is_exception)
else:
printWarning(pkg, 'dangling-symlink', f, link)
linktop=r.group(1)
r=absolute_regex.search(f)
if r:
filetop=r.group(1)
if filetop == linktop:
# absolute links within one toplevel directory are _not_ ok!
printWarning(pkg ,'symlink-should-be-relative', f, link)
# relative link
else:
if not is_so:
file = '%s%s/%s' % (pkg.dirName(), os.path.dirname(f), link)
file = os.path.normpath(file)
pkgfile = '%s/%s' % (os.path.dirname(f), link)
pkgfile = os.path.normpath(pkgfile)
if not (files.has_key(pkgfile) or os.path.exists(file)):
is_exception=0
for e in dangling_exceptions:
if e[0].search(link):
is_exception=e[1]
break
if is_exception:
if not is_exception in map(lambda x: x[0], pkg.requires() + pkg.prereq()):
printWarning(pkg, 'no-dependancy-on', is_exception)
else:
printWarning(pkg, 'dangling-relative-symlink', f, link)
pathcomponents=string.split(f, '/')[1:]
r=points_regex.search(link)
lastpop=None
mylink=None
while r:
mylink=r.group(1)
if len(pathcomponents) == 0:
printError(pkg, 'symlink-has-too-many-up-segments', f, link)
break
else:
lastpop=pathcomponents[0]
pathcomponents=pathcomponents[1:]
r=points_regex.search(mylink)
if mylink and lastpop:
r=absolute2_regex.search(mylink)
linktop=r.group(1)
# does the link go up and then down into the same directory?
#if linktop == lastpop:
# printWarning(pkg, 'lengthy-symlink', f, link)
if len(pathcomponents) == 0:
# we've reached the root directory
if linktop != lastpop:
# relative link into other toplevel directory
printWarning(pkg, 'symlink-should-be-absolute', f, link)
# check additional segments for mistakes like `foo/../bar/'
for linksegment in string.split(mylink, '/'):
if linksegment == '..':
printError(pkg, 'symlink-contains-up-and-down-segments', f, link)
if lib_package and lib_file and non_lib_file:
printError(pkg, 'outside-libdir-files', non_lib_file)
# Create an object to enable the auto registration of the test
check=FilesCheck()
if Config.info:
addDetails(
'no-documentation',
'''The package contains no documentation (README, doc, etc).
You have to include documentation files.''',
'not-listed-as-documentation',
'''The documentation files of this package are not listed with
the standard %doc tag.''',
'non-standard-uid',
'''A file in this package is owned by a non standard owner.
Standard owners are:
- root - bin
- daemon - adm
- lp - sync
- shutdown - halt
- mail - news
- uucp - operator
- games - gopher
- ftp - nobody
- nobody - lists
- gdm - xfs
- apache - postgres
- rpcuser - rpm''',
'non-standard-gid',
'''A file in this package is owned by a non standard group.
Standard groups are:
- root - bin - dip
- daemon - sys - ftp
- adm - tty - smb
- disk - lp - cdrom
- mem - kmem - pppusers
- wheel - floppy - cdwriter
- mail - news - audio
- uucp - man - dos
- games - gopher - nobody
- users - console - utmp
- lists - gdm - xfs
- popusers - slipusers - slocate
- x10 - urpmi - apache
- postgres - rpcuser - rpm''',
'library-without-ldconfig-postin',
'''This package contains a library and provides no %post with a call to ldconfig.''',
'postin-without-ldconfig',
'''This package contains a library and its %post doesn't call ldconfig.''',
'library-without-ldconfig-postun',
'''This package contains a library and provides no %postun with a call to ldconfig.''',
'postun-without-ldconfig',
'''This package contains a library and its %postun doesn't call ldconfig.''',
'info-files-without-install-info-postin',
'''This package contains info files and provides no %post with a call to install-info.''',
'postin-without-install-info',
'''This package contains info files and its %post doesn't call install-info.''',
'info-files-without-install-info-postun',
'''This package contains info files and provides no %postun with a call to install-info.''',
'postun-without-install-info',
'''This package contains info files and its %postun doesn't call install-info.''',
'perl-temp-file',
'''You have a perl temporary file in your package. Usually, this
file is beginning with a dot (.) and contain "perl" in its name.''',
'dir-or-file-in-tmp',
'''A file in the package is located in /tmp. It's not permitted
to put a file in this directory.''',
'dir-or-file-in-mnt',
'''A file in the package is located in /mnt. It's not permitted
to put a file in this directory.''',
'dir-or-file-in-opt',
'''A file in the package is located in /opt. It's not permitted
to put a file in this directory.''',
'subdir-in-bin',
'''The package contains a sub-directory in the /usr/bin. You can't
create a subdir there. Create it in /usr/lib/ instead.''',
'backup-file-in-package',
'''You have a backup file in your package. The files are usually
beginning with ~ (vi) or #file# (emacs). Please remove it and rebuild
your package.''',
'dir-or-file-in-home',
'''A file in the package is located in /home. It's not permitted
to put a file in this directory.''',
'cvs-internal-file',
'''You have file(s) from your CVS build directory. Move your CVS directory
out of the package and rebuild it.''',
'info-dir-file',
'''You have /usr/info/dir or /usr/share/info/dir in your package. It's not allowed.
Please remove it and rebuild your package.''',
'non-conffile-in-etc',
'''A file in your package is being installed in /etc, but is not a
configuration file. All the files in /etc MUST be configuration files
(add the %config option to the file in the spec file).''',
'compressed-symlink-with-wrong-ext',
'''The symlink points to a compressed file but doesn't use the same extension.''',
'setuid-gid-binary',
'''The file is setuid and setgid. Usually this is a bug. Otherwise, please contact
<flepied@mandrakesoft.com> about this so that this error gets included
in the exception file for rpmlint. With that, rpmlint will ignore
this bug in the future.''',
'setuid-binary',
'''The file is setuid. Usually this is a bug. Otherwise, please contact
<flepied@mandrakesoft.com> about this so that this error gets included
in the exception file for rpmlint. With that, rpmlint will ignore
this bug in the future.''',
'setgid-binary',
'''The file is setgid. Usually this is a bug. Otherwise, please contact
<flepied@mandrakesoft.com> about this so that this error gets included
in the exception file for rpmlint. With that, rpmlint will ignore
this bug in the future.''',
'non-standard-executable-perm',
'''A standard executable should have permission set to 0755. If you get this message,
that means that you have a wrong executable permission in your package.''',
'non-executable-in-bin',
'''A file is being installed in /usr/bin, but is not an executable. Be sure
that the file is an executable or that it has executable permissions.''',
'devel-file-in-non-devel-package',
'''A development file (usually source code) is located in a non-devel
package. If you want to include source code in your package, be sure to
create a development package.''',
'non-standard-dir-perm',
'''A standard directory should have permission set to 0755. If you get this message,
that means that you have a wrong directory permission in your package.''',
'standard-dir-owned-by-package',
'''This package owns a directory that is part of the standard hierarchy and this
can lead to default directory rights, owner or group be changed to something
non standard.''',
'no-dependancy-on',
'''
''',
'dangling-symlink',
'''The symbolik link points nowhere.''',
'symlink-should-be-relative',
'''
''',
'dangling-relative-symlink',
'''The relative symbolik link points nowhere.''',
'symlink-has-too-many-up-segments',
'''
''',
'symlink-should-be-absolute',
'''
''',
'symlink-contains-up-and-down-segments',
'''
''',
'non-readable',
'''The file can't be read by everybody. If this is normal (for security reason), send an
email to flepied@mandrakesoft.com to add it to the list of exceptions in the next release.''',
'incoherent-logrotate-file',
'''Your logrotate file should be named /etc/logrotate.d/<package name>.''',
'non-root-user-log-file',
'''If you need non root log file, just create a subdir in /var/log and put your files inside.''',
'non-root-group-log-file',
'''If you need non root log file, just create a subdir in /var/log and put your files inside.''',
'non-ghost-file',
'''File should be tagged %ghost.''',
'outside-libdir-files',
'''This library package must not contain non library files to allow 64
and 32 bits versions of the package to coexist.''',
'hidden-file-or-dir',
'''The file or directory is hidden. You should see if this is normal,
and delete it if needed.''',
'module-without-depmod-postin',
'''This package contains a kernel module but provides no call to depmod in %post.''',
'postin-with-wrong-depmod',
'''This package contains a kernel module but its %post calls depmod for the wrong kernel.''',
'module-without-depmod-postun',
'''This package contains a kernel module but provides no call to depmod in %postun.''',
'postun-with-wrong-depmod',
'''This package contains a kernel module but its %postun calls depmod for the wrong kernel.''',
)
# FilesCheck.py ends here