pax_global_header00006660000000000000000000000064141554064260014521gustar00rootroot0000000000000052 comment=a270e84d89c0869c350f66f322d4d5989a68daf8 rpmlint-2.2.0+ds1/000077500000000000000000000000001415540642600136725ustar00rootroot00000000000000rpmlint-2.2.0+ds1/.dockerignore000066400000000000000000000000611415540642600163430ustar00rootroot00000000000000**/__pycache__ **/*.pyo **/*.pyc !test/pyc/*.pyc rpmlint-2.2.0+ds1/.editorconfig000066400000000000000000000003501415540642600163450ustar00rootroot00000000000000[*] indent_style = space indent_size = 4 tab_width = 8 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true max_line_length = 79 [Makefile] indent_style = tab [*.{yml,yaml}] indent_size = 2 rpmlint-2.2.0+ds1/.github/000077500000000000000000000000001415540642600152325ustar00rootroot00000000000000rpmlint-2.2.0+ds1/.github/workflows/000077500000000000000000000000001415540642600172675ustar00rootroot00000000000000rpmlint-2.2.0+ds1/.github/workflows/main.yml000066400000000000000000000074131415540642600207430ustar00rootroot00000000000000name: CI on: push: branches: [main, opensuse] pull_request: branches: [main, opensuse] jobs: CI: runs-on: ubuntu-latest strategy: matrix: build-type: ['normal'] container: - 'registry.fedoraproject.org/fedora:latest' - 'registry.fedoraproject.org/fedora:rawhide' - 'registry.opensuse.org/opensuse/leap-dnf:15.3' - 'registry.opensuse.org/opensuse/tumbleweed-dnf:latest' include: - container: 'registry.fedoraproject.org/fedora:latest' build-type: 'no-optional-deps' - container: 'registry.opensuse.org/opensuse/tumbleweed-dnf:latest' build-type: 'no-optional-deps' fail-fast: false container: image: ${{ matrix.container }} options: --security-opt seccomp=unconfined steps: - run: dnf --assumeyes install cpio gzip bzip2 xz binutils glibc glibc-32bit glibc-locale python3-magic python3-rpm python3-base python3-setuptools python3-pybeam python3-pytest python3-pytest-cov python3-pytest-flake8 python3-pytest-xdist python3-flake8 python3-flake8-builtins python3-flake8-bugbear python3-flake8-import-order python3-flake8-quotes python3-pyxdg python3-zstd python3-toml python3-pip rpm-build git if: ${{ contains(matrix.container, 'opensuse') }} - run: dnf --assumeyes install checkbashisms dash desktop-file-utils appstream-glib myspell-en_US myspell-cs_CZ python3-pyenchant if: ${{ contains(matrix.container, 'opensuse') && matrix.build-type == 'normal' }} - run: dnf --assumeyes install python3-flake8-comprehensions if: ${{ contains(matrix.container, 'opensuse/tumbleweed') }} - run: dnf --nogpgcheck --assumeyes install /usr/bin/cpio /usr/bin/bzip2 /usr/bin/python3 /usr/bin/readelf /usr/bin/ldd /usr/bin/c++filt /usr/bin/xz glibc glibc.i686 python3-setuptools python3-magic python3-rpm python3-pybeam python3-pytest python3-pytest-cov python3-pytest-flake8 python3-pytest-xdist python3-flake8 python3-flake8-import-order python3-pyxdg python3-toml python3-zstd python3-pip rpm-build git if: ${{ contains(matrix.container, 'fedora') }} - run: dnf --nogpgcheck --assumeyes install /usr/bin/appstream-util /usr/bin/desktop-file-validate dash devscripts-checkbashisms hunspell-en hunspell-cs python3-enchant if: ${{ contains(matrix.container, 'fedora') && matrix.build-type == 'normal' }} - run: rm -rf $(rpm --eval '%_dbpath') if: matrix.build-type == 'no-optional-deps' - run: pip install coveralls - uses: actions/checkout@v2 - run: pytest - run: python3 -m cProfile -o profile.stats lint.py -V test/source/* test/binary/* > /dev/null - run: python3 test/dump_stats.py profile.stats - name: Collect the coveralls report env: COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: coveralls if: github.event_name != 'pull_request' rpmlint-2.2.0+ds1/.gitignore000066400000000000000000000005231415540642600156620ustar00rootroot00000000000000# Distribution / packaging .eggs *.egg-info build/ dist/ # Byte-compiled / optimized __pycache__/ *.py[cod] # Environments .env .venv env/ venv/ # Unit test / coverage reports .pytest_cache .tox/ .coverage .coverage.* .cache # IDEs .idea # Patches and files *.orig *.rej .*.swp # Others *.flog *.xz /__version__.py /.packit/*.tar.gz rpmlint-2.2.0+ds1/.packit.yaml000066400000000000000000000021051415540642600161050ustar00rootroot00000000000000--- # docs: https://packit.dev/docs/configuration/ upstream_package_name: rpmlint specfile_path: .packit/rpmlint.spec actions: get-current-version: "rpmspec -q --srpm --qf '%{VERSION}' .packit/rpmlint.spec" jobs: - job: copr_build metadata: targets: - fedora-rawhide-x86_64 - fedora-rawhide-aarch64 - mageia-cauldron-x86_64 - mageia-cauldron-aarch64 - opensuse-tumbleweed-x86_64 - opensuse-tumbleweed-aarch64 trigger: pull_request - job: copr_build trigger: commit metadata: targets: - fedora-rawhide-x86_64 - fedora-rawhide-aarch64 - mageia-cauldron-x86_64 - mageia-cauldron-aarch64 - opensuse-tumbleweed-x86_64 - opensuse-tumbleweed-aarch64 branch: main project: rpm-software-management-rpmlint-mainline list_on_homepage: True preserve_project: True - job: copr_build trigger: commit metadata: targets: - opensuse-tumbleweed-x86_64 - opensuse-tumbleweed-aarch64 branch: opensuse project: rpm-software-management-rpmlint-opensuse list_on_homepage: True preserve_project: True rpmlint-2.2.0+ds1/.packit/000077500000000000000000000000001415540642600152235ustar00rootroot00000000000000rpmlint-2.2.0+ds1/.packit/rpmlint.spec000066400000000000000000000053501415540642600175670ustar00rootroot00000000000000%{!?python3: %global python3 %{__python3}} Name: rpmlint Version: 2.2.0 Release: 0%{?dist} Summary: Tool for checking common errors in RPM packages License: GPLv2+ URL: https://github.com/rpm-software-management/rpmlint Source0: %{url}/archive/%{version}/%{name}-%{version}.tar.gz BuildArch: noarch BuildRequires: python3-devel %if 0%{?suse_version} # Unfortunately, these don't get pulled in automatically... BuildRequires: python-rpm-macros BuildRequires: python3-setuptools # For tests BuildRequires: python3-magic BuildRequires: python3-pybeam BuildRequires: python3-pyenchant BuildRequires: python3-pytest BuildRequires: python3-pytest-cov BuildRequires: python3-pytest-flake8 BuildRequires: python3-pytest-xdist BuildRequires: python3-pyxdg BuildRequires: python3-rpm BuildRequires: python3-toml BuildRequires: python3-zstd %else BuildRequires: python3dist(setuptools) # For tests BuildRequires: python3dist(file-magic) BuildRequires: python3dist(pybeam) BuildRequires: python3dist(pyenchant) BuildRequires: python3dist(pytest) BuildRequires: python3dist(pytest-cov) BuildRequires: python3dist(pytest-flake8) BuildRequires: python3dist(pytest-xdist) BuildRequires: python3dist(pyxdg) BuildRequires: python3dist(rpm) BuildRequires: python3dist(toml) BuildRequires: python3dist(zstd) %endif # Rest of the test dependencies BuildRequires: dash BuildRequires: /usr/bin/appstream-util BuildRequires: /usr/bin/checkbashisms BuildRequires: /usr/bin/desktop-file-validate %if 0%{?suse_version} BuildRequires: myspell-en_US BuildRequires: myspell-cs_CZ %else BuildRequires: hunspell-en BuildRequires: hunspell-cs %endif %if 0%{?fedora} || 0%{?rhel} >= 8 BuildRequires: glibc-langpack-en %endif %if 0%{?suse_version} BuildRequires: glibc-locale-base %endif %if 0%{?mageia} BuildRequires: locales-en %endif Requires: /bin/bash Requires: /usr/bin/appstream-util Requires: /usr/bin/bzip2 Requires: /usr/bin/checkbashisms Requires: /usr/bin/cpio Requires: /usr/bin/desktop-file-validate Requires: /usr/bin/groff Requires: /usr/bin/gtbl Requires: /usr/bin/ldd Requires: /usr/bin/man Requires: /usr/bin/perl Requires: /usr/bin/readelf Requires: /usr/bin/xz Requires: /usr/bin/zstd # Enable Python dependency generation %{?python_enable_dependency_generator} %description rpmlint is a tool for checking common errors in RPM packages. Binary and source packages as well as spec files can be checked. %prep %autosetup %build %py3_build %install %py3_install %check %python3 -m pytest %files %license COPYING %doc README* %{_bindir}/rpmlint %{_bindir}/rpmdiff %{python3_sitelib}/rpmlint* %changelog rpmlint-2.2.0+ds1/.vscode/000077500000000000000000000000001415540642600152335ustar00rootroot00000000000000rpmlint-2.2.0+ds1/.vscode/launch.json000066400000000000000000000004451415540642600174030ustar00rootroot00000000000000{ "version": "0.2.0", "configurations": [ { "name": "Debug", "type": "python", "request": "launch", "program": "lint.py", "console": "integratedTerminal", "args": ["./test/binary/*.rpm"] } ] } rpmlint-2.2.0+ds1/.vscode/settings.json000066400000000000000000000002241415540642600177640ustar00rootroot00000000000000{ "python.testing.pytestArgs": [ "test" ], "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, } rpmlint-2.2.0+ds1/COPYING000066400000000000000000000432541415540642600147350ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. rpmlint-2.2.0+ds1/MANIFEST.in000066400000000000000000000002421415540642600154260ustar00rootroot00000000000000include Makefile include COPYING include README.md include conftest.py recursive-include configs *.toml recursive-include rpmlint *.toml recursive-include test * rpmlint-2.2.0+ds1/Makefile000066400000000000000000000001771415540642600153370ustar00rootroot00000000000000all: rpmlint/__isocodes__.py clean: rm -f rpmlint/__isocodes__.py rpmlint/__isocodes__.py: tools/generate-isocodes.py > $@ rpmlint-2.2.0+ds1/README.md000066400000000000000000000131551415540642600151560ustar00rootroot00000000000000# rpmlint [![Build and Test](https://github.com/rpm-software-management/rpmlint/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/rpm-software-management/rpmlint/actions/workflows/main.yml) [![Build and Test 2](https://github.com/rpm-software-management/rpmlint/actions/workflows/main.yml/badge.svg?branch=opensuse)](https://github.com/rpm-software-management/rpmlint/actions/workflows/main.yml) [![Total alerts](https://img.shields.io/lgtm/alerts/g/rpm-software-management/rpmlint.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/rpm-software-management/rpmlint/alerts/) [![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/rpm-software-management/rpmlint.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/rpm-software-management/rpmlint/context:python) [![Coverage Status](https://coveralls.io/repos/github/rpm-software-management/rpmlint/badge.svg?branch=main)](https://coveralls.io/github/rpm-software-management/rpmlint?branch=main) `rpmlint` is a tool for checking common errors in RPM packages. `rpmlint` can be used to test individual packages before uploading or to check an entire distribution. `rpmlint` can check binary RPMs, source RPMs, and plain specfiles, but all checks do not apply to all argument types. For best check coverage, run `rpmlint` on source RPMs instead of plain specfiles. The idea for `rpmlint` is from the lintian tool of the Debian project. All the checks reside in `rpmlint/checks` folder. Feel free to provide new checks and suggestions at: https://github.com/rpm-software-management/rpmlint ## Install For installation on your machine you will need the following packages: Mandatory: - Python 3.6 or newer - python3-setuptools, python3-toml, python3-pyxdg, python3-beam - rpm and its python bindings - binutils, cpio, gzip, bzip, xz and zstd Optional, for running the test suite: - devscripts - dash - a 32-bit glibc if on a 64-bit architecture - desktop-file-utils - libmagic and its python bindings - enchant and its python bindings, along with en_US and cs_CZ dictionaries - appstream-util, part of appstream-glib `rpmlint` is part of most distributions and as an user you can simply dnf install rpmlint ## Testing You will need to have all the required modules as listed on the Install section above. You will also need `pytest`,`pytest-cov`, `pytest-xdist`, and `pytest-flake8`. If all the dependencies are present you can just execute tests using: `python3 -m pytest` Or even pick one of the tests using `pytest`: `python3 -m pytest test/test_config.py` ## Bugfixing and contributing Any help is, of course, welcome but honestly most probable cause for your visit here is that `rpmlint` is marking something as invalid while it shouldn't or it is marking something as correct while it should not either :) Now there is an easy way how to fix that. Our testsuite simply needs an extension to take the above problem into the account. Primarily we just need the offending rpm file (best the smallest you can find or we would soon take few GB to take a checkout) and some basic expectation of what should happen. ### Example workflow 1) I have rpmfile that should report unreadable zip file 2) I store this file in git under `test/binary/texlive-codepage-doc-2018.151.svn21126-38.1.noarch.rpm` 3) Now I need to figure out what `check` should test this, in this case `test_zip.py` 4) For the testing I will have to devise a small function that validates my expectations: ``` @pytest.mark.parametrize('package', ['binary/texlive-codepage-doc']) def test_zip2(tmpdir, package, zipcheck): output, test = zipcheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'W: unable-to-read-zip' in out ``` As you can see it is not so hard and with each added test we get better coverage on what is really expected from rpmlint and avoid naughty regressions in the long run. Preferable approach for binary packages is to create artificial testcase (to keep binaries small and trivial). We are currently using OBS to produce binaries: https://build.opensuse.org/project/show/devel:openSUSE:Factory:rpmlint:tests For a sample package see: https://build.opensuse.org/package/show/devel:openSUSE:Factory:rpmlint:tests/non-position-independent-exec ## Configuration If you want to change configuration options or the list of checks you can use the following locations: `/etc/xdg/rpmlint/*toml` `$XDG_CONFIG_HOME/rpmlint/*toml` The configuration itself is a `toml` file where for some basic inspiration you can check up [`rpmlint/configdefaults.toml`](rpmlint/configdefaults.toml) which specifies format/defaults. One can also include additional configuration files (or directories) by using the `--config` option. Note that all TOML configuration values are merged and not overridden. So e.g. values in a list are concatenated. If you need an override, use `*.override.*toml` configuration file, where all defined values are selected as default. Additional option to control `rpmlint` behaviour is the addition of `rpmlintrc` file which uses old syntax for compatibility with old `rpmlint` releases, yet it can be normal `toml` file if you wish: setBadness('check', 0) addFilter('test-i-ignore') The location of `rpmlintrc` can be set using `--rpmlintrc` option. Or you can have any `*.rpmlintrc` or `*-rpmlintrc` file in the current working directory. The best practice is to store the name in `$PACKAGE_NAME.rpmlintrc`. `setBadness` overrides a default badness for a given check and `addFilter` ignores all errors that match the given regular expression (one cannot filter out errors that are listed in `BlockedFilters` in a configuration file). rpmlint-2.2.0+ds1/configs/000077500000000000000000000000001415540642600153225ustar00rootroot00000000000000rpmlint-2.2.0+ds1/configs/Fedora/000077500000000000000000000000001415540642600165225ustar00rootroot00000000000000rpmlint-2.2.0+ds1/configs/Fedora/fedora.toml000066400000000000000000000351441415540642600206660ustar00rootroot00000000000000# Fedora's configuration for the rpmlint utility. # When checking that various files that should be compressed are # indeed compressed, look for this filename extension CompressExtension = "gz" # simple error is enough; warnings are fine BadnessThreshold = -1 # Whether to allow packaging kernel modules in non-kernel packages. KernelModuleRPMsOK = false # Maximum allowed line length for Summary and Description tags MaxLineLength = 80 # Assumed default version of Python if one cannot be determined from files # FIXME this should be sys.version[:3] but I have no idea how to implement it # here without changing it every other release PythonDefaultVersion = "" # Regexp string with expected suffix in Release tags. ReleaseExtension = '\.(fc|rhe?l|el)\d+(?=\.|$)' # Whether to want default start/stop runlevels specified in init scripts UseDefaultRunlevels = false ValidSrcPerms = [ "0o644", "0o664", ] # List of directories considered to be system default library search paths. SystemLibPaths = [ "/lib", "/usr/lib", "/lib64", "/usr/lib64", ] # Enabled checks for the rpmlint to be run (besides the default set) Checks = [ "BashismsCheck", "PAMModulesCheck", "TmpFilesCheck", "SysVInitOnSystemdCheck", "SharedLibraryPolicyCheck", ] # Interpreters whose scriptlets are allowed to be empty ValidEmptyShells = [ "/usr/sbin/ldconfig", ] # Package scriptlet interpreters ValidShells = [ "", "/usr/bin/sh", "/usr/bin/bash", "/usr/sbin/ldconfig", "/usr/bin/perl", "/usr/bin/python", "/usr/bin/python3", ] Filters = [ # FIXME - the commented lines are from openSUSE config # Are they relevant for Fedora too? # PR which enables them or remove them is welcome ## Stuff autobuild takes care about # '.*invalid-version.*', # '.*invalid-packager.*', '.*not-standard-release-extension.*', # '.*invalid-buildhost.*', '.*executable-in-library-package.*', '.*non-versioned-file-in-library-package.*', '.*shlib-policy-name-error.*', # '.*hardcoded-path-in-buildroot-tag.*', '.*no-buildroot-tag.*', # '.*cross-directory-hard-link.*', # Do not validate package rpm groups '.*devel-package-with-non-devel-group.*', '.*no-group-tag.*', '.*non-standard-group.*', # Output filters # '.*spurious-bracket-in-.*', # '.*one-line-command-in-.*', # ' dir-or-file-in-opt ', # handled by CheckFilelist.py # ' dir-or-file-in-usr-local ', # handled by CheckFilelist.py ' non-standard-dir-in-usr ', # handled by CheckFilelist.py ' no-signature', # ' symlink-crontab-file', #bnc591431 # ' without-chkconfig', # 'unstripped-binary-or-object.*\.ko', # ' no-chkconfig', # ' subsys-not-used', # ' dangerous-command.*', # ' setuid-binary.*', # 'subdir-in-bin /sbin/conf.d/', # '.* nss_db non-standard-dir-in-var db', # 'non-standard-dir-in-usr openwin', # 'ibcs2 non-standard-dir-in-usr i486-sysv4', # 'shlibs5 non-standard-dir-in-usr i486-linux-libc5', # 'explicit-lib-dependency libtool', # ## Filesystem package needs special exceptions # '^filesystem\..*: dir-or-file-in-var-run', # '^filesystem\..*: dir-or-file-in-var-lock', # '^filesystem\..*: dir-or-file-in-var-tmp', # '^filesystem\..*: dir-or-file-in-var-run', # '^filesystem\..*: dir-or-file-in-var-lock', # '^filesystem\..*: dir-or-file-in-usr-tmp', # '^filesystem\..*: dir-or-file-in-tmp', # '^filesystem\..*: dir-or-file-in-mnt', # '^filesystem\..*: dir-or-file-in-home', # '^filesystem\..*: hidden-file-or-dir /root/.gnupg', # '^filesystem\..*: hidden-file-or-dir /root/.gnupg', # '^filesystem\..*: hidden-file-or-dir /etc/skel/.config', # '^filesystem\..*: hidden-file-or-dir /etc/skel/.local', # '^filesystem\..*: hidden-file-or-dir /tmp/.X11-unix', # '^filesystem\..*: hidden-file-or-dir /tmp/.ICE-unix', # '^filesystem\..*: hidden-file-or-dir /etc/skel/.fonts', # '^filesystem\..*: filelist-forbidden-fhs23', # '^filesystem\..*: filelist-forbidden-opt', # '^filesystem\..*: non-standard-uid /var/lib/nobody nobody', # '^filesystem\..*: missing-dependency-to-cron', ## has arch specific dirs in /usr # '^filesystem\..*: no-binary', # ## Suppress any errors about internal packages # '^qa\S+: [EWI]:', # '^\S*(?:INTERNAL|internal)\.\S+: [EWI]:', # ## Exceptions for devel-files # 'devel-file-in-non-devel-package.*/boot/vmlinuz-.*autoconf.h', # 'devel-file-in-non-devel-package.*/usr/src/linux-', # 'devel-file-in-non-devel-package.*/usr/share/systemtap', # '-(?:examples|doc)\.\S+: \w: devel-file-in-non-devel-package', # 'java\S+-demo\.\S+: \w: devel-file-in-non-devel-package', # 'avr-libc\.\S+: \w: devel-file-in-non-devel-package', # 'cross-.*devel-file-in-non-devel-package', # 'cmake.*devel-file-in-non-devel-package', # 'gcc\d\d.*devel-file-in-non-devel-package', # 'OpenOffice_org-sdk\.\S+: \w: devel-file-in-non-devel-package', # 'wnn-sdk\.\S+: \w: devel-file-in-non-devel-package', # 'ocaml\.\S+: \w: devel-file-in-non-devel-package', # 'xorg-x11-server-sdk\.\S+: \w: devel-file-in-non-devel-package', # 'linux-kernel-headers\.\S+: \w: devel-file-in-non-devel-package', # ' devel-file-in-non-devel-package.*-config', # 'libtool\.\S+: \w: devel-file-in-non-devel-package', # 'sdb.* dangling-relative-symlink /usr/share/doc/sdb/.*/gifs ../gifs', # 'kernel-modules-not-in-kernel-packages', # ## SUSE kmp's don't need manual depmod (bnc#456048) # 'module-without-depmod-postin', # 'postin-with-wrong-depmod', # 'module-without-depmod-postun', # 'postun-with-wrong-depmod', # 'configure-without-libdir-spec', # 'conffile-without-noreplace-flag /etc/init.d', # 'use-of-RPM_SOURCE_DIR', # 'use-tmp-in-', # 'symlink-contains-up-and-down-segments /var/lib/named', # 'no-ldconfig-symlink', # 'aaa_base\.\S+: \w: use-of-home-in-%post', # 'description-line-too-long', 'hardcoded-library-path', # ## Doesn't seem to make sense # 'invalid-ldconfig-symlink', # 'invalid-soname', # 'library-not-linked-against-libc', # 'only-non-binary-in-usr-lib', 'outside-libdir-files', # ## We want these files # ' perl-temp-file ', # ' hidden-file-or-dir .*/\.packlist', # ' hidden-file-or-dir .*/\.directory', # 'perl-.*no-binary', ' no-major-in-name ', # ## We check for that already # 'dangling-relative-symlink', ' lib-package-without-%mklibname', ' requires-on-release', # ' non-executable-script /etc/profile.d/', # ' non-executable-script /var/adm/fillup-templates/', # ' init-script-name-with-dot ', # '.* statically-linked-binary /sbin/ldconfig', # '.* statically-linked-binary /sbin/init', # 'valgrind.* statically-linked-binary', # 'ldconfig-post.*/ddiwrapper/wine/', # 'glibc\.\S+: \w: statically-linked-binary /usr/sbin/glibc_post_upgrade', ' symlink-should-be-relative ', # ' binary-or-shlib-defines-rpath .*ORIGIN', # 'libzypp.*shlib-policy-name-error.*libzypp', # 'libtool.*shlib-policy.*', # ## Stuff that is currently too noisy, but might become relevant in the future # ' prereq-use', # ' file-not-utf8', # ' tag-not-utf8', # ' setup-not-quiet', # ' mixed-use-of-spaces-and-tabs ', # ' prereq-use ', # ## An issue with OBS, works with autobuild ' no-packager-tag', # ' unversioned-explicit-provides ', # ' unversioned-explicit-obsoletes ', # ' service-default-enabled ', # ' non-standard-dir-perm ', # ' conffile-without-noreplace-flag ', # ' non-standard-executable-perm ', ' jar-not-indexed ', # ' uncompressed-zip ', # ' %ifarch-applied-patch ', # ' read-error ', # ' init-script-without-chkconfig-postin ', # ' init-script-without-chkconfig-preun ', # ' postin-without-chkconfig ', # ' preun-without-chkconfig ', ' no-dependency-on locales', ' no-dependency-on perl-base', ' no-dependency-on python-base', ' python-naming-policy-not-applied', # FIXME does this really exists? ' perl-naming-policy-not-applied', # ' shlib-policy-name-error', # ' binary-or-shlib-defines-rpath', # ' executable-marked-as-config-file', # ' log-files-without-logrotate', # ' hardcoded-prefix-tag', ' -debug(info|source).* no-documentation', # ' multiple-specfiles', # ' no-default-runlevel ', # ' setgid-binary ', # ' non-readable ', ' postin-without-ghost-file-creation ', # ## Exceptions for filelist checks # 'nfs-client\.\S+: \w: filelist-forbidden-backup-file /var/lib/nfs/sm.bak', # 'perl\.\S+: \w: filelist-forbidden-perl-dir ', # 'info\.\S+: \w: info-dir-file .*/usr/share/info/dir', # ## These packages are used for CD creation and are not supposed to be ## installed. It's still a dirty hack to make an exception. The ## packages should either be built in a separate project with ## different config or file be put somewhere below /opt/suse/* # '(?:dosutils|skelcd|installation-images|yast2-slide-show|instlux|skelcd-.*|patterns-.*)\.\S+: \w: filelist-forbidden-fhs23 /CD1', # ## Too noisy, and usually not something downstream packagers can fix # ' incorrect-fsf-address ', # ' no-manual-page-for-binary ', # ' static-library-without-debuginfo /usr/lib(?:64)?/ghc-[\d\.]+/', # ## Many places have shorter paths # ' non-coherent-filename ', # Mandriva specific stuff that Fedora do not want either ' invalid-build-requires ', # Fedora specific stuff that we don't want ' ghost-files-without-postin', ' no-provides ', ' -debuginfo.* /usr/lib/debug/', ' -debugsource.* /usr/src/debug/', '^gpg-pubkey:', ' doc-file-dependency .* /bin/sh$', 'explicit-lib-dependency (liberation-fonts|libertas-.*-firmware|libvirt$|.*-(java|python|utils)$)', 'explicit-lib-dependency (python-.*lib.*|python2-.*lib.*|python3-.*lib.*)$', 'explicit-lib-dependency libreoffice.*$', 'dangling-\S*symlink /usr/share/doc/HTML/\S+/common .+/common$', 'hidden-file-or-dir .*/man5/\.k5login\.5[^/]+$', 'blender.+ (wrong-script-interpreter|non-executable-script) .+/blender/.+\.py.*BPY.*', # Fedora 12 and newer no longer need a buildroot defined, to have the buildroot cleaned at the beginning # of %install, and do not need to define a %clean section unless the default is invalid. ' no-cleaning-of-buildroot ', # Only EL4 needs the files-attr-not-set check, because rpm 4.4 and newer no longer need a %defattr line # (it automatically provides one). 'files-attr-not-set', # Don't bother with the non-ghost-in-run checks, /var/lock and /var/run are # symlinks to /run/lock and /run respectively, and /run is a tmpfs 'non-ghost-in-run', # Someone thought it was a good idea to make .desktop files executable. They were wrong. # Nevertheless, I do not yet control the universe, so we squelch the error here. 'script-without-shebang .*\.desktop$', # Some files in /etc/ are not meant to be modified by the sysadmin 'non-conffile-in-etc /etc/rpm/.*$', # Files that are intentionally not supposed to be readable # Contains passwords 'non-readable /etc/ovirt-engine/isouploader.conf', ## Ignore webservers which are just broken. 'invalid-url .*\.googlecode\.com/.*HTTP Error 404', 'invalid-url .*\.jboss\.org/.*HTTP Error 403', 'invalid-url .*bitbucket\.org/.*HTTP Error 403', 'invalid-url .*github\.com/.*HTTP Error 403', # Don't care about long descriptions on debuginfo packages # They automatically include the package name and are always # quite long. '-debuginfo.* description-line-too-long', # ignore "common" jargon words # https://bugzilla.redhat.com/show_bug.cgi?id=1424684#c9 'spelling-error.* \b(runtime|Runtime|metadata|cryptographic|multi|linux|filesystem|filesystems|backend|backends|userspace|addon|wayland|Wayland|util|utils|lossless|virtualization|toolkits|libvirtd|crypto|glyphs|GStreamer|http|extensibility|codec|codecs|truetype|scalable|pluggable|pixbuf|Kerberos|customizable|bitstream|tcp|libXss|libs|libc|encodings|GLib|udev|posix|libpng|glapi|gbm|freedesktop|spi|realtime|preprocessor|libaudit|hypervisor|embeddable|distributable|devel|config|cairo|bootloader|adaptors|pragma|passphrase|malloc|libvirt|libmagic|io|datetime|boolean|argparse|py|pinentry|namespace|middleware|lowlevel|libxcb|libudev|libsoup|libgcrypt|libcom|iSCSI|initramfs|GObject|executables|dialogs|checkpolicy|bitmapped|assistive|btrfs|crypttab|defrag|dracut|hostname|luks|mountpoints|netdev|rpmnew|rpmsave|storaged|tss|unlocker)\b', # Fedora no longer uses explicit ldconfig %post/%postun as of Fedora 28 'library-without-ldconfig-postin', 'library-without-ldconfig-postun', # Ignore 700 dir perms here 'non-standard-dir-perm /etc/.* 700', 'non-standard-dir-perm /var/lib/.* 700', # pip 20.2 generates PEP 376 "REQUESTED" marker (empty) 'zero-length .+/site-packages/.+\.dist-info/REQUESTED\b', # py.typed files are empty 'zero-length .+/site-packages/.+/py\.typed\b', # https://bugzilla.redhat.com/496737, https://bugzilla.redhat.com/646455 'coreutils.* (setuid-binary|non-standard-executable-perm) /bin/su (root )?04', 'krb5-workstation.* (setuid-binary|non-standard-executable-perm) /usr/kerberos/bin/ksu (root )?04', 'passwd.* (setuid-binary|non-standard-executable-perm) /usr/bin/passwd (root )?04', 'sudo.* (setuid-binary|non-standard-executable-perm) /usr/bin/sudo(edit)? (root )?04', 'upstart.* (setuid-binary|non-standard-executable-perm) /sbin/initctl (root )?04', 'usermode.* (setuid-binary|non-standard-executable-perm) /usr/sbin/userhelper (root )?04', ## Bash completion files are not scripts, do not require them marked as %config # 'W: non-conffile-in-etc /etc/bash_completion.d/', # # Info uses file triggers now (boo#1152169) ' info-files-without-install-info-postin', ' info-files-without-install-info-postun ', ' postin-without-install-info ', ] [DanglingSymlinkExceptions."/usr/share/doc/licenses/"] path = "/usr/share/doc/licenses/" name = "licenses" [DanglingSymlinkExceptions."consolehelper$"] path = "consolehelper$" name = "usermode" [DanglingSymlinkExceptions."consolehelper-gtk$"] path = "consolehelper-gtk$" name = "usermode-gtk" [Descriptions] non-standard-uid = '''A file in this package is owned by an unregistered user id. To register the user, please make a pull request to the rpmlint config file configs/Fedora/fedora.toml in the rpmlint repository. ''' non-standard-gid = '''A file in this package is owned by an unregistered group id. To register the group, please make a pull request to the rpmlint config file configs/Fedora/fedora.toml in the rpmlint repository. ''' no-changelogname-tag = '''There is no changelog. Please insert a '%changelog' section heading in your spec file and prepare your changelog entry using e.g. the 'rpmdev-bumpspec' command.''' rpmlint-2.2.0+ds1/configs/Fedora/licenses.toml000066400000000000000000000117431415540642600212320ustar00rootroot00000000000000# taken from Fedora's rpmlint 1.11 # FIXME should be revisited and updated # Package scriptlet interpreters ValidLicenses = [ # These are the short names for all of the Fedora approved licenses. # The master list is kept here: http://fedoraproject.org/wiki/Licensing # Last synced with revision "2.53, May 27, 2021" of that page. # Note that Bcotton is no longer bumping revision. '0BSD', 'AAL', 'Abstyles', 'Adobe', 'ADSL', 'AFL', 'Afmparse', 'AGPLv1', 'AGPLv3', 'AGPLv3+', 'AGPLv3 with exceptions', 'AMDPLPA', 'AML', 'AMPAS BSD', 'ANTLR-PD', 'APAFML', 'App-s2p', 'APSL 2.0', 'ARL', 'Array', 'Artistic 2.0', 'Artistic clarified', 'ASL 1.0', 'ASL 1.1', 'ASL 2.0', 'Bahyph', 'Barr', 'Beerware', 'BeOpen', 'Bibtex', 'BitTorrent', 'Boost', 'Borceux', 'BSD', 'BSD-2-Clause-Patent', 'BSD Protection', 'BSD with advertising', 'BSD with attribution', 'CATOSL', 'CC0', 'CeCILL', 'CeCILL-B', 'CeCILL-C', 'CDDL-1.0', 'CDDL-1.1', 'CNRI', 'Condor', 'Copyright only', 'CPAL', 'CPL', 'CPM', 'CRC32', 'Crossword', 'Crystal Stacker', 'Cube', 'diffmark', 'DMIT', 'DOC', 'Dotseqn', 'DSDP', 'dvipdfm', 'DWPL', 'ECL 1.0', 'ECL 2.0', 'eCos', 'EFL 2.0', 'eGenix', 'Entessa', 'EPICS', 'EPL-1.0', 'EPL-2.0', 'ERPL', 'EU Datagrid', 'EUPL 1.1', 'EUPL 1.2', 'Eurosym', 'Fair', 'FDK-AAC', 'FSFAP', 'FSFUL', 'FSFULLR', 'FTL', 'Giftware', 'GL2PS', 'Glide', 'Glulxe', 'gnuplot', 'GPL+', 'GPL+ or Artistic', 'GPL+ with exceptions', 'GPLv1', 'GPLv2 or Artistic', 'GPLv2+ or Artistic', 'GPLv2', 'GPLv2 with exceptions', 'GPLv2+', 'GPLv2+ with exceptions', 'GPLv3', 'GPLv3 with exceptions', 'GPLv3+', 'GPLv3+ with exceptions', 'HaskellReport', 'HSRL', 'IBM', 'IJG', 'ImageMagick', 'iMatix', 'Imlib2', 'Inner-Net', 'Intel ACPI', 'Interbase', 'ISC', 'Jabber', 'JasPer', 'JPython', 'Julius', 'Knuth', 'Latex2e', 'LBNL BSD', 'Leptonica', 'LGPLv2', 'LGPLv2 with exceptions', 'LGPLv2+', 'LGPLv2+ or Artistic', 'LGPLv2+ with exceptions', 'LGPLv3', 'LGPLv3 with exceptions', 'LGPLv3+', 'LGPLv3+ with exceptions', 'Lhcyr', 'libtiff', 'LLGPL', 'Logica', 'LOSLA', 'LPL', 'LPPL', 'MakeIndex', 'mecab-ipadic', 'midnight', 'Minpack', 'MirOS', 'MIT', 'MIT-0', 'MITNFA', 'MIT with advertising', 'mod_macro', 'Motosoto', 'MPLv1.0', 'MPLv1.1', 'MPLv2.0', 'MS-PL', 'MS-RL', 'MTLL', 'Mup', 'Naumen', 'NCSA', 'NetCDF', 'Netscape', 'Newmat', 'Newsletr', 'NGPL', 'NISTSL', 'NLPL', 'Nmap', 'Nokia', 'NOSL', 'Noweb', 'OGL', 'OML', 'OpenLDAP', 'OpenPBS', 'OpenSSL', 'OReilly', 'OSL 1.0', 'OSL 1.1', 'OSL 2.0', 'OSL 2.1', 'OSL 3.0', 'Par', 'Phorum', 'PHP', 'PlainTeX', 'Plexus', 'PostgreSQL', 'psfrag', 'psutils', 'Public Domain', 'Python', 'Qhull', 'QPL', 'radvd', 'Rdisc', 'REX', 'RiceBSD', 'Romio', 'RPSL', 'RSA', 'Rsfs', 'Ruby', 'Saxpath', 'Sequence', 'SCEA', 'SCRIP', 'Sendmail', 'Sleepycat', 'SISSL', 'SLIB', 'SNIA', 'softSurfer', 'SPL', 'STMPL', 'SWL', 'TCGL', 'TCL', 'Teeworlds', 'TGPPL', 'TGPPL with exceptions', 'Threeparttable', 'TMate', 'Tolua', 'TORQUEv1.1', 'TOSL', 'TPDL', 'TPL', 'TTWL', 'Tumbolia', 'UCAR', 'UCD', 'Unicode', 'Unlicense', 'UPL', 'Vim', 'VNLSL', 'VOSTROM', 'VSL', 'W3C', 'Webmin', 'Wsuipa', 'WTFPL', 'wxWidgets', 'wxWindows', 'Xerox', 'xinetd', 'xpp', 'XSkat', 'YPLv1.1', 'Zed', 'Zend', 'zlib', 'zlib with acknowledgement', 'ZPLv1.0', 'ZPLv2.0', 'ZPLv2.1', # Documentation licenses 'CDL', 'FBSDDL', 'GFDL', 'IEEE', 'LDPL', 'OFSFDL', 'Open Publication', 'Public Use', 'Verbatim', # Content licenses 'CC-BY', 'CC-BY-ND', 'CC-BY-SA', 'DL-DE-BY', 'DMTF', 'DSL', 'EFML', 'Free Art', 'GeoGratis', 'Green OpenMusic', 'OAL', 'Ouverte', 'PDDL-1.0', # Font licenses 'AMS', 'Arphic', 'AHFL', 'Baekmuk', 'Bitstream Vera', 'Charter', 'DoubleStroke', 'ec', 'Elvish', 'Hershey', 'HOFL', 'IPA', 'Liberation', 'Lucida', 'MgOpen', 'mplus', 'OFL', 'PTFL', 'Punknova', 'STIX', 'Utopia', 'Wadalab', 'XANO', # Others 'Redistributable, no modification permitted', 'Freely redistributable without restriction', ] rpmlint-2.2.0+ds1/configs/Fedora/scoring.toml000066400000000000000000000003321415540642600210610ustar00rootroot00000000000000[Scoring] # This can set how bad each error is. # But we set BadnessThreshold to -1 so anything above 0 # is actually fatal. # You can check configs/openSUSE/scoring.toml for more fine graded scoring. no-group-tag = 1 rpmlint-2.2.0+ds1/configs/Fedora/users-groups.toml000066400000000000000000000055751415540642600221110ustar00rootroot00000000000000# generated using tools/generate-fedora-users-groups.py on 2021-03-23 StandardUsers = ['pkiuser', 'sshd', 'cyrus', 'keystone', 'squid', 'lp', 'root', 'dovecot', 'oprofile', 'ldap', 'arpwatch', 'retrace', 'vdsm', 'nut', 'hacluster', 'polkituser', 'mailman', 'saned', 'adm', 'mailnull', 'rtkit', 'postfix', 'cimsrvr', 'postgres', 'vhostmd', 'smmsp', 'dbus', 'rpcuser', 'nslcd', 'named', 'radvd', 'ntp', 'systemd-resolve', 'nova', 'tss', 'wildfly', 'ricci', 'mysql', 'apache', 'usbmuxd', 'systemd-network', 'vcsa', 'sabayon', 'quantum', 'katello', 'xfs', 'halt', 'tomcat', 'beagleindex', 'jbosson-agent', 'fax', 'haldaemon', 'pulse', 'hsqldb', 'cassandra', 'pegasus', 'clamav', 'piranha', 'mongodb', 'netdump', 'activemq', 'ovirtagent', 'bin', 'aeolus', 'sync', 'radiusd', 'rpm', 'webalizer', 'nocpulse', 'elasticsearch', 'games', 'pvm', 'wnn', 'snortd', 'privoxy', 'nscd', 'gdm', 'prelude-manager', 'cinder', 'shutdown', 'rpc', 'condor', '#systemd-journal-gateway', 'heat', 'qemu', 'myproxy', 'avahi', 'operator', 'majordomo', 'puppet', 'exim', 'sanlock', 'rhevm', 'swift', 'wallaby', 'ftp', 'ident', 'frontpage', 'ats', 'mail', 'ceilometer', 'news', 'distcache', 'ovirt', 'ceph', 'desktop', 'uucp', 'glance', 'jonas', 'haproxy', 'abrt', 'quagga', 'stap-server', 'dhcpd', 'nobody', 'luci', 'bacula', 'avahi-autoipd', 'gopher', 'tcpdump', 'daemon', 'amandabackup', 'jetty'] StandardGroups = ['pkiuser', 'sshd', 'keystone', 'squid', 'pppusers', 'kvm', 'popusers', 'lp', 'root', 'dovecot', 'oprofile', 'disk', 'ldap', 'arpwatch', 'retrace', 'nut', 'polkituser', 'audio', 'mailman', 'stapusr', 'saned', 'adm', 'mailnull', 'rtkit', 'postfix', 'utmp', 'cimsrvr', 'wheel', 'postgres', 'vhostmd', 'smmsp', 'realtime', 'kmem', 'rpcuser', 'dbus', 'screen', 'utempter', 'video', 'named', 'radvd', 'ntp', 'man', 'systemd-resolve', 'nova', 'tss', 'sys', 'cdrom', 'wildfly', 'ricci', 'mysql', 'apache', 'usbmuxd', 'jbosson', 'systemd-network', 'vcsa', 'console', 'sabayon', 'quantum', 'katello', 'haclient', 'xfs', 'tomcat', 'beagleindex', 'fax', 'haldaemon', 'systemd-journal', 'pulse', 'hsqldb', 'cassandra', 'pegasus', 'clamav', 'piranha', 'mongodb', 'netdump', 'activemq', 'ovirtagent', 'bin', 'saslauth', 'aeolus', 'radiusd', 'mem', 'rpm', 'webalizer', 'floppy', 'nocpulse', 'elasticsearch', 'games', 'pvm', 'wnn', 'tty', 'snortd', 'slipusers', 'nscd', 'gdm', 'privoxy', 'mock', 'prelude-manager', 'cinder', 'dialout', 'rpc', 'condor', '#systemd-journal-gateway', 'heat', 'qemu', 'stapsys', 'avahi', 'myproxy', 'majordomo', 'tape', 'puppet', 'exim', 'sanlock', 'rhevm', 'swift', 'wallaby', 'ftp', 'ident', 'frontpage', 'ats', 'mail', 'ceilometer', 'news', 'distcache', 'stapdev', 'users', 'ovirt', 'ceph', 'desktop', 'uucp', 'glance', 'jonas', 'postdrop', 'haproxy', 'abrt', 'quagga', 'stap-server', 'lock', 'dhcpd', 'nobody', 'wbpriv', 'luci', 'quaggavt', 'bacula', 'avahi-autoipd', 'gopher', 'wine', 'tcpdump', 'dip', 'daemon', 'slocate', 'jetty'] rpmlint-2.2.0+ds1/configs/Fedora/warn-on-functions.toml000066400000000000000000000023461415540642600230130ustar00rootroot00000000000000# Additional warnings on specific function calls [WarnOnFunction] [WarnOnFunction.crypto-policy-non-compliance-openssl] f_name = "SSL_CTX_set_cipher_list" good_param = "PROFILE=SYSTEM" description = """This application package calls a function to explicitly set crypto ciphers for SSL/TLS. That may cause the application not to use the system-wide set cryptographic policy and should be modified in accordance to: https://fedoraproject.org/wiki/Packaging:CryptoPolicies""" [WarnOnFunction.crypto-policy-non-compliance-gnutls-1] f_name = "gnutls_priority_set_direct" description = """This application package calls a function to explicitly set crypto ciphers for SSL/TLS. That may cause the application not to use the system-wide set cryptographic policy and should be modified in accordance to: https://fedoraproject.org/wiki/Packaging:CryptoPolicies""" [WarnOnFunction.crypto-policy-non-compliance-gnutls-2] f_name = "gnutls_priority_init" good_param = "SYSTEM" description = """This application package calls a function to explicitly set crypto ciphers for SSL/TLS. That may cause the application not to use the system-wide set cryptographic policy and should be modified in accordance to: https://fedoraproject.org/wiki/Packaging:CryptoPolicies""" rpmlint-2.2.0+ds1/configs/openSUSE/000077500000000000000000000000001415540642600167635ustar00rootroot00000000000000rpmlint-2.2.0+ds1/configs/openSUSE/dbus-services.toml000066400000000000000000000221301415540642600224340ustar00rootroot00000000000000DBUSServices.WhiteList = [ "cups.conf", # bnc#515977 # # the following are not audited. We accept them as legacy for now # # upower "org.freedesktop.UPower.service", "org.freedesktop.UPower.conf", # PackageKit "org.freedesktop.PackageKit.conf", # PackageKit "org.freedesktop.PackageKit.service", # NetworkManager-pptp "nm-pptp-service.conf", # gdm "gdm.conf", # udisks2 (bnc#742751) "org.freedesktop.UDisks2.service", "org.freedesktop.UDisks2.conf", # scmon "com.novell.Pkcs11Monitor.conf", # systemd (bnc#641924) "org.freedesktop.systemd1.service", "org.freedesktop.systemd1.conf", "org.freedesktop.hostname1.service", "org.freedesktop.hostname1.conf", "org.freedesktop.login1.conf", "org.freedesktop.login1.service", "org.freedesktop.timedate1.conf", "org.freedesktop.timedate1.service", "org.freedesktop.locale1.conf", "org.freedesktop.locale1.service", # gconf2 "org.gnome.GConf.Defaults.service", "org.gnome.GConf.Defaults.conf", # system-config-printer (bnc#694640) "com.redhat.NewPrinterNotification.conf", "com.redhat.PrinterDriversInstaller.conf", # rtkit "org.freedesktop.RealtimeKit1.conf", "org.freedesktop.RealtimeKit1.service", # wpa_supplicant "fi.epitest.hostap.WPASupplicant.service", # bnc#681116 "fi.w1.wpa_supplicant1.service", "wpa_supplicant.conf", # kdebase4-workspace "org.kde.fontinst.service", "org.kde.fontinst.conf", "org.kde.ksysguard.processlisthelper.service", "org.kde.kcontrol.kcmclock.service", "org.kde.kcontrol.kcmclock.conf", "org.kde.ksysguard.processlisthelper.conf", # pulseaudio "pulseaudio-system.conf", # avahi "avahi-dbus.conf", "org.freedesktop.Avahi.service", # hp-drive-guard "hp-drive-guard-dbus.conf", # NetworkManager "nm-dispatcher.conf", "org.freedesktop.nm_dispatcher.service", # bnc#747780 "org.freedesktop.NetworkManager.conf", # bluez (bnc#768062) "bluetooth.conf", "org.bluez.service", # dnsmasq "dnsmasq.conf", # gypsy "Gypsy.conf", "org.freedesktop.Gypsy.service", # pommed "pommed.conf", # NetworkManager-openvpn "nm-openvpn-service.conf", # polkit "org.freedesktop.PolicyKit1.conf", "org.freedesktop.PolicyKit1.service", # cups-pk-helper "org.opensuse.CupsPkHelper.Mechanism.service", "org.opensuse.CupsPkHelper.Mechanism.conf", # NetworkManager-vpnc "nm-vpnc-service.conf", # NetworkManager-strongswan, bnc#656222 "nm-strongswan-service.conf", # mumble, bnc#660784 "mumble-server.conf", # kdebase4-runtime, bnc#672145 "org.kde.powerdevil.backlighthelper.service", "org.kde.powerdevil.backlighthelper.conf", # urfkill (bnc#688328) "org.freedesktop.URfkill.service", "org.freedesktop.URfkill.conf", # account services (bnc#676638) "org.freedesktop.Accounts.service", "org.freedesktop.Accounts.conf", # colord (bnc#698250) "org.freedesktop.ColorManager.service", "org.freedesktop.ColorManager.conf", # lightdm (bnc#708205) "org.freedesktop.DisplayManager.conf", # sddm (boo#897788) "sddm_org.freedesktop.DisplayManager.conf", # NetworkManager-openvpn (bnc#732915) "nm-openconnect-service.conf", # snapper (bnc#759391) "org.opensuse.Snapper.conf", "org.opensuse.Snapper.service", # autofs-udisk interaction (bnc#782691) "org.freedesktop.AutoMount.conf", # NetworkManager-iodine (bnc#781071) "nm-iodine-service.conf", # new ModemManager (bnc#798273) "org.freedesktop.ModemManager1.conf", "org.freedesktop.ModemManager1.service", # fprintd 0.4.1 (finger print dbus service) (bnc#792095) "net.reactivated.Fprint.service", "net.reactivated.Fprint.conf", # wicked network management (bnc#783932) "org.opensuse.Network.conf", "org.opensuse.Network.AUTO4.conf", "org.opensuse.Network.DHCP6.conf", "org.opensuse.Network.DHCP4.conf", "org.opensuse.Network.Nanny.conf", # systemd machined service (bnc#828207) "org.freedesktop.machine1.service", "org.freedesktop.machine1.conf", # systemd importd service (bnc#964935) "org.freedesktop.import1.service", "org.freedesktop.import1.conf", # GeoClue2 DBUS Service (bnc#838360) "org.freedesktop.GeoClue2.service", "org.freedesktop.GeoClue2.conf", # GeoClue2 DBUS Service more (bnc#862216) "org.freedesktop.GeoClue2.Agent.conf", # mate dbus serice (bnc#831404) "org.mate.SettingsDaemon.DateTimeMechanism.service", "org.mate.SettingsDaemon.DateTimeMechanism.conf", # tuned DBUS service (bnc#787379, bnc#1007279) "com.redhat.tuned.conf", # bluez (bnc#768062) "bluetooth.conf", "org.bluez.service", # kwallet (bnc#1033296) "org.kde.kcontrol.kcmkwallet5.conf", "org.kde.kcontrol.kcmkwallet5.service", # neard (bnc#837978) "org.neard.conf", # oFono (bnc#862354) "ofono.conf", # libKF5Auth4 (bnc#864716) "org.kde.kf5auth.conf", # firewalld (bnc#907625) "FirewallD.conf", # systemd networkd (bnc#918799) "org.freedesktop.network1.conf", "org.freedesktop.network1.service", # realmd (bnc#916766) "org.freedesktop.realmd.service", "org.freedesktop.realmd.conf", # teamd (bnc#941993) "org.libteam.teamd.conf", # cinnamon settings daemon (bsc#951830) "org.cinnamon.SettingsDaemon.DateTimeMechanism.conf", "org.cinnamon.SettingsDaemon.DateTimeMechanism.service", # thermald (bsc#954771) "org.freedesktop.thermald.conf", "org.freedesktop.thermald.service", # iio-sensor-proxy (bsc#939191) "net.hadess.SensorProxy.conf", # TEMPORARY APPROVAL ONLY (meissner 20160519) tcmu-runner (bsc#978903) "tcmu-runner.conf", "org.kernel.TCMUService1.service", # sysprof (bsc#996111) "org.gnome.Sysprof2.service", "org.gnome.Sysprof2.conf", # sysprof (bsc#1151418) "org.gnome.Sysprof3.service", "org.gnome.Sysprof3.conf", # flatpak (bsc#984817) "org.freedesktop.Flatpak.SystemHelper.service", "org.freedesktop.Flatpak.SystemHelper.conf", # systemd resolver, but dont add automatically to nsswitch.conf! (bsc#917781) "org.freedesktop.resolve1.conf", "org.freedesktop.resolve1.service", # powerdevil discretegpuhelper (bsc#1019748) "org.kde.powerdevil.discretegpuhelper.conf", "org.kde.powerdevil.discretegpuhelper.service", # rebootmgr (bsc#1019644) "org.opensuse.RebootMgr.conf", # blueman (bsc#987141) "org.blueman.Mechanism.conf", "org.blueman.Mechanism.service", # os-autoinst (bsc#1032649) "org.opensuse.os_autoinst.switch.conf", # backintime (bsc#1007723, bsc#1032717) "net.launchpad.backintime.serviceHelper.conf", "net.launchpad.backintime.serviceHelper.service", # switchroo-control (bsc#1034309) "net.hadess.SwitcherooControl.conf", # pam_dbus (bsc#1039709). Take care to # never enable/integrate this by default (see bsc comments) "pam_dbus.conf", # tpm2-abrmd (bnc#1049694) "tpm2-abrmd.conf", "com.intel.tss2.Tabrmd.service", # nfs-ganesha (bsc#997880) "org.ganesha.nfsd.conf", # NetworkManager-l2tp (bsc#846337) "nm-l2tp-service.conf", # fwupd (bsc#932807) "org.freedesktop.fwupd.conf", "org.freedesktop.fwupd.service", # connman (bsc#1057697) "connman-nmcompat.conf", "connman.conf", "connman-vpn-dbus.conf", "net.connman.vpn.service", # kcmsddm (bsc#1065563) "org.kde.kcontrol.kcmsddm.conf", "org.kde.kcontrol.kcmsddm.service", # usbauth (bsc#1066877) "org.opensuse.usbauth.conf", # kalarm (bnc#1087714, renamed from kalarmrtcwake) "org.kde.kalarm.rtcwake.conf", "org.kde.kalarm.rtcwake.service", # NetworkManager-libreswan (bnc#1089340) "nm-libreswan-service.conf", # libratbag (bnc#1076467) "org.freedesktop.ratbag1.service", "org.freedesktop.ratbag1.conf", # xpra (bsc#1102836) "xpra.conf", # iwd (bsc#1108037) "net.connman.iwd.service", "iwd-dbus.conf", # NetworkManager-fortisslvpn (bsc#1109938) "nm-fortisslvpn-service.conf", # systemd-timesyncd (bsc#1111254) "org.freedesktop.timesync1.service", "org.freedesktop.timesync1.conf", # keepalived (bsc#1015141) "org.keepalived.Vrrp1.conf", # boltd (bsc#1119975) "org.freedesktop.bolt.conf", "org.freedesktop.bolt.service", # certmonger (bsc#1129452) "org.fedorahosted.certmonger.service", "certmonger.conf", # systemd-portabled (boo#1145639) "org.freedesktop.portable1.service", "org.freedesktop.portable1.conf", # sssd (bsc#1157663, bsc#1106600) "org.freedesktop.sssd.infopipe.service", "org.freedesktop.sssd.infopipe.conf", # oddjob (bsc#1169494) "oddjob.conf", "oddjob-mkhomedir.conf", # libvirt-dbus (bsc#1173093) "org.libvirt.service", "org.libvirt.conf", # powerdevil chargethreshold helper (bsc#1176474) "org.kde.powerdevil.chargethresholdhelper.service", "org.kde.powerdevil.chargethresholdhelper.conf", # plasma5-disks smartmon helper "org.kde.kded.smart.service", "org.kde.kded.smart.conf" ] rpmlint-2.2.0+ds1/configs/openSUSE/licenses.toml000066400000000000000000000537371415540642600215040ustar00rootroot00000000000000# Generated with generate-suse-licenses.py script: ValidLicenses = [ "0BSD", "0BSD+", "AAL", "AAL+", "ADSL", "ADSL+", "AFL-1.1", "AFL-1.1+", "AFL-1.2", "AFL-1.2+", "AFL-2.0", "AFL-2.0+", "AFL-2.1", "AFL-2.1+", "AFL-3.0", "AFL-3.0+", "AGPL-1.0-only", "AGPL-1.0-only+", "AGPL-1.0-or-later", "AGPL-1.0-or-later+", "AGPL-3.0-only", "AGPL-3.0-only+", "AGPL-3.0-or-later", "AGPL-3.0-or-later+", "AMDPLPA", "AMDPLPA+", "AML", "AML+", "AMPAS", "AMPAS+", "ANTLR-PD", "ANTLR-PD+", "ANTLR-PD-fallback", "ANTLR-PD-fallback+", "APAFML", "APAFML+", "APL-1.0", "APL-1.0+", "APSL-1.0", "APSL-1.0+", "APSL-1.1", "APSL-1.1+", "APSL-1.2", "APSL-1.2+", "APSL-2.0", "APSL-2.0+", "Abstyles", "Abstyles+", "Adobe-2006", "Adobe-2006+", "Adobe-Glyph", "Adobe-Glyph+", "Afmparse", "Afmparse+", "Aladdin", "Aladdin+", "Apache-1.0", "Apache-1.0+", "Apache-1.1", "Apache-1.1+", "Apache-2.0", "Apache-2.0+", "Artistic-1.0", "Artistic-1.0+", "Artistic-1.0 OR GPL-1.0-or-later", "Artistic-1.0-Perl", "Artistic-1.0-Perl+", "Artistic-1.0-cl8", "Artistic-1.0-cl8+", "Artistic-2.0", "Artistic-2.0+", "BSD-1-Clause", "BSD-1-Clause+", "BSD-2-Clause", "BSD-2-Clause+", "BSD-2-Clause-Patent", "BSD-2-Clause-Patent+", "BSD-2-Clause-Views", "BSD-2-Clause-Views+", "BSD-3-Clause", "BSD-3-Clause+", "BSD-3-Clause-Attribution", "BSD-3-Clause-Attribution+", "BSD-3-Clause-Clear", "BSD-3-Clause-Clear+", "BSD-3-Clause-LBNL", "BSD-3-Clause-LBNL+", "BSD-3-Clause-Modification", "BSD-3-Clause-Modification+", "BSD-3-Clause-No-Military-License", "BSD-3-Clause-No-Military-License+", "BSD-3-Clause-No-Nuclear-License", "BSD-3-Clause-No-Nuclear-License+", "BSD-3-Clause-No-Nuclear-License-2014", "BSD-3-Clause-No-Nuclear-License-2014+", "BSD-3-Clause-No-Nuclear-Warranty", "BSD-3-Clause-No-Nuclear-Warranty+", "BSD-3-Clause-Open-MPI", "BSD-3-Clause-Open-MPI+", "BSD-4-Clause", "BSD-4-Clause+", "BSD-4-Clause-Shortened", "BSD-4-Clause-Shortened+", "BSD-4-Clause-UC", "BSD-4-Clause-UC+", "BSD-Protection", "BSD-Protection+", "BSD-Source-Code", "BSD-Source-Code+", "BSL-1.0", "BSL-1.0+", "BUSL-1.1", "BUSL-1.1+", "Bahyph", "Bahyph+", "Barr", "Barr+", "Beerware", "Beerware+", "BitTorrent-1.0", "BitTorrent-1.0+", "BitTorrent-1.1", "BitTorrent-1.1+", "BlueOak-1.0.0", "BlueOak-1.0.0+", "Borceux", "Borceux+", "C-UDA-1.0", "C-UDA-1.0+", "CAL-1.0", "CAL-1.0+", "CAL-1.0-Combined-Work-Exception", "CAL-1.0-Combined-Work-Exception+", "CATOSL-1.1", "CATOSL-1.1+", "CC-BY-1.0", "CC-BY-1.0+", "CC-BY-2.0", "CC-BY-2.0+", "CC-BY-2.5", "CC-BY-2.5+", "CC-BY-2.5-AU", "CC-BY-2.5-AU+", "CC-BY-3.0", "CC-BY-3.0+", "CC-BY-3.0-AT", "CC-BY-3.0-AT+", "CC-BY-3.0-DE", "CC-BY-3.0-DE+", "CC-BY-3.0-NL", "CC-BY-3.0-NL+", "CC-BY-3.0-US", "CC-BY-3.0-US+", "CC-BY-4.0", "CC-BY-4.0+", "CC-BY-NC-1.0", "CC-BY-NC-1.0+", "CC-BY-NC-2.0", "CC-BY-NC-2.0+", "CC-BY-NC-2.5", "CC-BY-NC-2.5+", "CC-BY-NC-3.0", "CC-BY-NC-3.0+", "CC-BY-NC-3.0-DE", "CC-BY-NC-3.0-DE+", "CC-BY-NC-4.0", "CC-BY-NC-4.0+", "CC-BY-NC-ND-1.0", "CC-BY-NC-ND-1.0+", "CC-BY-NC-ND-2.0", "CC-BY-NC-ND-2.0+", "CC-BY-NC-ND-2.5", "CC-BY-NC-ND-2.5+", "CC-BY-NC-ND-3.0", "CC-BY-NC-ND-3.0+", "CC-BY-NC-ND-3.0-DE", "CC-BY-NC-ND-3.0-DE+", "CC-BY-NC-ND-3.0-IGO", "CC-BY-NC-ND-3.0-IGO+", "CC-BY-NC-ND-4.0", "CC-BY-NC-ND-4.0+", "CC-BY-NC-SA-1.0", "CC-BY-NC-SA-1.0+", "CC-BY-NC-SA-2.0", "CC-BY-NC-SA-2.0+", "CC-BY-NC-SA-2.0-FR", "CC-BY-NC-SA-2.0-FR+", "CC-BY-NC-SA-2.0-UK", "CC-BY-NC-SA-2.0-UK+", "CC-BY-NC-SA-2.5", "CC-BY-NC-SA-2.5+", "CC-BY-NC-SA-3.0", "CC-BY-NC-SA-3.0+", "CC-BY-NC-SA-3.0-DE", "CC-BY-NC-SA-3.0-DE+", "CC-BY-NC-SA-3.0-IGO", "CC-BY-NC-SA-3.0-IGO+", "CC-BY-NC-SA-4.0", "CC-BY-NC-SA-4.0+", "CC-BY-ND-1.0", "CC-BY-ND-1.0+", "CC-BY-ND-2.0", "CC-BY-ND-2.0+", "CC-BY-ND-2.5", "CC-BY-ND-2.5+", "CC-BY-ND-3.0", "CC-BY-ND-3.0+", "CC-BY-ND-3.0-DE", "CC-BY-ND-3.0-DE+", "CC-BY-ND-4.0", "CC-BY-ND-4.0+", "CC-BY-SA-1.0", "CC-BY-SA-1.0+", "CC-BY-SA-2.0", "CC-BY-SA-2.0+", "CC-BY-SA-2.0-UK", "CC-BY-SA-2.0-UK+", "CC-BY-SA-2.1-JP", "CC-BY-SA-2.1-JP+", "CC-BY-SA-2.5", "CC-BY-SA-2.5+", "CC-BY-SA-3.0", "CC-BY-SA-3.0+", "CC-BY-SA-3.0-AT", "CC-BY-SA-3.0-AT+", "CC-BY-SA-3.0-DE", "CC-BY-SA-3.0-DE+", "CC-BY-SA-4.0", "CC-BY-SA-4.0+", "CC-PDDC", "CC-PDDC+", "CC0-1.0", "CC0-1.0+", "CDDL-1.0", "CDDL-1.0+", "CDDL-1.1", "CDDL-1.1+", "CDL-1.0", "CDL-1.0+", "CDLA-Permissive-1.0", "CDLA-Permissive-1.0+", "CDLA-Permissive-2.0", "CDLA-Permissive-2.0+", "CDLA-Sharing-1.0", "CDLA-Sharing-1.0+", "CECILL-1.0", "CECILL-1.0+", "CECILL-1.1", "CECILL-1.1+", "CECILL-2.0", "CECILL-2.0+", "CECILL-2.1", "CECILL-2.1+", "CECILL-B", "CECILL-B+", "CECILL-C", "CECILL-C+", "CERN-OHL-1.1", "CERN-OHL-1.1+", "CERN-OHL-1.2", "CERN-OHL-1.2+", "CERN-OHL-P-2.0", "CERN-OHL-P-2.0+", "CERN-OHL-S-2.0", "CERN-OHL-S-2.0+", "CERN-OHL-W-2.0", "CERN-OHL-W-2.0+", "CNRI-Jython", "CNRI-Jython+", "CNRI-Python", "CNRI-Python+", "CNRI-Python-GPL-Compatible", "CNRI-Python-GPL-Compatible+", "COIL-1.0", "COIL-1.0+", "CPAL-1.0", "CPAL-1.0+", "CPL-1.0", "CPL-1.0+", "CPOL-1.02", "CPOL-1.02+", "CUA-OPL-1.0", "CUA-OPL-1.0+", "Caldera", "Caldera+", "ClArtistic", "ClArtistic+", "Community-Spec-1.0", "Community-Spec-1.0+", "Condor-1.1", "Condor-1.1+", "Crossword", "Crossword+", "CrystalStacker", "CrystalStacker+", "Cube", "Cube+", "D-FSL-1.0", "D-FSL-1.0+", "DOC", "DOC+", "DRL-1.0", "DRL-1.0+", "DSDP", "DSDP+", "Dotseqn", "Dotseqn+", "ECL-1.0", "ECL-1.0+", "ECL-2.0", "ECL-2.0+", "EFL-1.0", "EFL-1.0+", "EFL-2.0", "EFL-2.0+", "EPICS", "EPICS+", "EPL-1.0", "EPL-1.0+", "EPL-2.0", "EPL-2.0+", "EUDatagrid", "EUDatagrid+", "EUPL-1.0", "EUPL-1.0+", "EUPL-1.1", "EUPL-1.1+", "EUPL-1.2", "EUPL-1.2+", "Entessa", "Entessa+", "ErlPL-1.1", "ErlPL-1.1+", "Eurosym", "Eurosym+", "FDK-AAC", "FDK-AAC+", "FSFAP", "FSFAP+", "FSFUL", "FSFUL+", "FSFULLR", "FSFULLR+", "FTL", "FTL+", "Fair", "Fair+", "Frameworx-1.0", "Frameworx-1.0+", "FreeBSD-DOC", "FreeBSD-DOC+", "FreeImage", "FreeImage+", "GD", "GD+", "GFDL-1.1-invariants-only", "GFDL-1.1-invariants-only+", "GFDL-1.1-invariants-or-later", "GFDL-1.1-invariants-or-later+", "GFDL-1.1-no-invariants-only", "GFDL-1.1-no-invariants-only+", "GFDL-1.1-no-invariants-or-later", "GFDL-1.1-no-invariants-or-later+", "GFDL-1.1-only", "GFDL-1.1-only+", "GFDL-1.1-or-later", "GFDL-1.1-or-later+", "GFDL-1.2-invariants-only", "GFDL-1.2-invariants-only+", "GFDL-1.2-invariants-or-later", "GFDL-1.2-invariants-or-later+", "GFDL-1.2-no-invariants-only", "GFDL-1.2-no-invariants-only+", "GFDL-1.2-no-invariants-or-later", "GFDL-1.2-no-invariants-or-later+", "GFDL-1.2-only", "GFDL-1.2-only+", "GFDL-1.2-or-later", "GFDL-1.2-or-later+", "GFDL-1.3-invariants-only", "GFDL-1.3-invariants-only+", "GFDL-1.3-invariants-or-later", "GFDL-1.3-invariants-or-later+", "GFDL-1.3-no-invariants-only", "GFDL-1.3-no-invariants-only+", "GFDL-1.3-no-invariants-or-later", "GFDL-1.3-no-invariants-or-later+", "GFDL-1.3-only", "GFDL-1.3-only+", "GFDL-1.3-or-later", "GFDL-1.3-or-later+", "GL2PS", "GL2PS+", "GLWTPL", "GLWTPL+", "GPL-1.0-only", "GPL-1.0-only+", "GPL-1.0-or-later", "GPL-1.0-or-later+", "GPL-2.0-only", "GPL-2.0-only+", "GPL-2.0-or-later", "GPL-2.0-or-later+", "GPL-3.0-only", "GPL-3.0-only+", "GPL-3.0-only WITH GCC-exception-3.1", "GPL-3.0-or-later", "GPL-3.0-or-later+", "GPL-3.0-or-later WITH Autoconf-exception-3.0", "GPL-3.0-with-Qt-Company-Qt-exception-1.1", "Giftware", "Giftware+", "Glide", "Glide+", "Glulxe", "Glulxe+", "HPND", "HPND+", "HPND-sell-variant", "HPND-sell-variant+", "HTMLTIDY", "HTMLTIDY+", "HaskellReport", "HaskellReport+", "Hippocratic-2.1", "Hippocratic-2.1+", "IBM-pibs", "IBM-pibs+", "ICU", "ICU+", "IJG", "IJG+", "IPA", "IPA+", "IPL-1.0", "IPL-1.0+", "ISC", "ISC+", "ImageMagick", "ImageMagick+", "Imlib2", "Imlib2+", "Info-ZIP", "Info-ZIP+", "Intel", "Intel+", "Intel-ACPI", "Intel-ACPI+", "Interbase-1.0", "Interbase-1.0+", "JPNIC", "JPNIC+", "JSON", "JSON+", "JasPer-2.0", "JasPer-2.0+", "LAL-1.2", "LAL-1.2+", "LAL-1.3", "LAL-1.3+", "LGPL-2.0-only", "LGPL-2.0-only+", "LGPL-2.0-or-later", "LGPL-2.0-or-later+", "LGPL-2.1-only", "LGPL-2.1-only+", "LGPL-2.1-or-later", "LGPL-2.1-or-later+", "LGPL-2.1-or-later WITH GCC-exception-2.0", "LGPL-2.1-with-Qt-Company-Qt-exception-1.1", "LGPL-3.0-only", "LGPL-3.0-only+", "LGPL-3.0-only WITH openvpn-openssl-exception", "LGPL-3.0-or-later", "LGPL-3.0-or-later+", "LGPL-3.0-with-Qt-Company-Qt-exception-1.1", "LGPLLR", "LGPLLR+", "LPL-1.0", "LPL-1.0+", "LPL-1.02", "LPL-1.02+", "LPPL-1.0", "LPPL-1.0+", "LPPL-1.1", "LPPL-1.1+", "LPPL-1.2", "LPPL-1.2+", "LPPL-1.3a", "LPPL-1.3a+", "LPPL-1.3c", "LPPL-1.3c+", "Latex2e", "Latex2e+", "Leptonica", "Leptonica+", "LiLiQ-P-1.1", "LiLiQ-P-1.1+", "LiLiQ-R-1.1", "LiLiQ-R-1.1+", "LiLiQ-Rplus-1.1", "LiLiQ-Rplus-1.1+", "Libpng", "Libpng+", "Linux-OpenIB", "Linux-OpenIB+", "Linux-man-pages-copyleft", "Linux-man-pages-copyleft+", "MIT", "MIT+", "MIT-0", "MIT-0+", "MIT-CMU", "MIT-CMU+", "MIT-Modern-Variant", "MIT-Modern-Variant+", "MIT-advertising", "MIT-advertising+", "MIT-enna", "MIT-enna+", "MIT-feh", "MIT-feh+", "MIT-open-group", "MIT-open-group+", "MITNFA", "MITNFA+", "MPL-1.0", "MPL-1.0+", "MPL-1.1", "MPL-1.1+", "MPL-2.0", "MPL-2.0+", "MPL-2.0-no-copyleft-exception", "MPL-2.0-no-copyleft-exception+", "MS-PL", "MS-PL+", "MS-RL", "MS-RL+", "MTLL", "MTLL+", "MakeIndex", "MakeIndex+", "MirOS", "MirOS+", "Motosoto", "Motosoto+", "MulanPSL-1.0", "MulanPSL-1.0+", "MulanPSL-2.0", "MulanPSL-2.0+", "Multics", "Multics+", "Mup", "Mup+", "NAIST-2003", "NAIST-2003+", "NASA-1.3", "NASA-1.3+", "NBPL-1.0", "NBPL-1.0+", "NCGL-UK-2.0", "NCGL-UK-2.0+", "NCSA", "NCSA+", "NGPL", "NGPL+", "NIST-PD", "NIST-PD+", "NIST-PD-fallback", "NIST-PD-fallback+", "NLOD-1.0", "NLOD-1.0+", "NLOD-2.0", "NLOD-2.0+", "NLPL", "NLPL+", "NOSL", "NOSL+", "NPL-1.0", "NPL-1.0+", "NPL-1.1", "NPL-1.1+", "NPOSL-3.0", "NPOSL-3.0+", "NRL", "NRL+", "NTP", "NTP+", "NTP-0", "NTP-0+", "Naumen", "Naumen+", "Net-SNMP", "Net-SNMP+", "NetCDF", "NetCDF+", "Newsletr", "Newsletr+", "Nokia", "Nokia+", "NonFree", "NonFree+", "Noweb", "Noweb+", "O-UDA-1.0", "O-UDA-1.0+", "OCCT-PL", "OCCT-PL+", "OCLC-2.0", "OCLC-2.0+", "ODC-By-1.0", "ODC-By-1.0+", "ODbL-1.0", "ODbL-1.0+", "OFL-1.0", "OFL-1.0+", "OFL-1.0-RFN", "OFL-1.0-RFN+", "OFL-1.0-no-RFN", "OFL-1.0-no-RFN+", "OFL-1.1", "OFL-1.1+", "OFL-1.1-RFN", "OFL-1.1-RFN+", "OFL-1.1-no-RFN", "OFL-1.1-no-RFN+", "OGC-1.0", "OGC-1.0+", "OGDL-Taiwan-1.0", "OGDL-Taiwan-1.0+", "OGL-Canada-2.0", "OGL-Canada-2.0+", "OGL-UK-1.0", "OGL-UK-1.0+", "OGL-UK-2.0", "OGL-UK-2.0+", "OGL-UK-3.0", "OGL-UK-3.0+", "OGTSL", "OGTSL+", "OLDAP-1.1", "OLDAP-1.1+", "OLDAP-1.2", "OLDAP-1.2+", "OLDAP-1.3", "OLDAP-1.3+", "OLDAP-1.4", "OLDAP-1.4+", "OLDAP-2.0", "OLDAP-2.0+", "OLDAP-2.0.1", "OLDAP-2.0.1+", "OLDAP-2.1", "OLDAP-2.1+", "OLDAP-2.2", "OLDAP-2.2+", "OLDAP-2.2.1", "OLDAP-2.2.1+", "OLDAP-2.2.2", "OLDAP-2.2.2+", "OLDAP-2.3", "OLDAP-2.3+", "OLDAP-2.4", "OLDAP-2.4+", "OLDAP-2.5", "OLDAP-2.5+", "OLDAP-2.6", "OLDAP-2.6+", "OLDAP-2.7", "OLDAP-2.7+", "OLDAP-2.8", "OLDAP-2.8+", "OML", "OML+", "OPL-1.0", "OPL-1.0+", "OPUBL-1.0", "OPUBL-1.0+", "OSET-PL-2.1", "OSET-PL-2.1+", "OSL-1.0", "OSL-1.0+", "OSL-1.1", "OSL-1.1+", "OSL-2.0", "OSL-2.0+", "OSL-2.1", "OSL-2.1+", "OSL-3.0", "OSL-3.0+", "OpenSSL", "OpenSSL+", "PDDL-1.0", "PDDL-1.0+", "PHP-3.0", "PHP-3.0+", "PHP-3.01", "PHP-3.01+", "PSF-2.0", "PSF-2.0+", "Parity-6.0.0", "Parity-6.0.0+", "Parity-7.0.0", "Parity-7.0.0+", "Plexus", "Plexus+", "PolyForm-Noncommercial-1.0.0", "PolyForm-Noncommercial-1.0.0+", "PolyForm-Small-Business-1.0.0", "PolyForm-Small-Business-1.0.0+", "PostgreSQL", "PostgreSQL+", "Python-2.0", "Python-2.0+", "QPL-1.0", "QPL-1.0+", "Qhull", "Qhull+", "RHeCos-1.1", "RHeCos-1.1+", "RPL-1.1", "RPL-1.1+", "RPL-1.5", "RPL-1.5+", "RPSL-1.0", "RPSL-1.0+", "RSA-MD", "RSA-MD+", "RSCPL", "RSCPL+", "Rdisc", "Rdisc+", "Ruby", "Ruby+", "SAX-PD", "SAX-PD+", "SCEA", "SCEA+", "SGI-B-1.0", "SGI-B-1.0+", "SGI-B-1.1", "SGI-B-1.1+", "SGI-B-2.0", "SGI-B-2.0+", "SHL-0.5", "SHL-0.5+", "SHL-0.51", "SHL-0.51+", "SISSL", "SISSL+", "SISSL-1.2", "SISSL-1.2+", "SMLNJ", "SMLNJ+", "SMPPL", "SMPPL+", "SNIA", "SNIA+", "SPL-1.0", "SPL-1.0+", "SSH-OpenSSH", "SSH-OpenSSH+", "SSH-short", "SSH-short+", "SSPL-1.0", "SSPL-1.0+", "SUSE-Arphic", "SUSE-Arphic+", "SUSE-BSD-3-Clause-with-non-nuclear-addition", "SUSE-BSD-3-Clause-with-non-nuclear-addition+", "SUSE-BSD-Mark-Modifications", "SUSE-BSD-Mark-Modifications+", "SUSE-Bitstream-Vera", "SUSE-Bitstream-Vera+", "SUSE-CC-Sampling-Plus-1.0", "SUSE-CC-Sampling-Plus-1.0+", "SUSE-CPL-0.5", "SUSE-CPL-0.5+", "SUSE-CacertRoot", "SUSE-CacertRoot+", "SUSE-Copyleft-Next-0.3.0", "SUSE-Copyleft-Next-0.3.0+", "SUSE-Curb", "SUSE-Curb+", "SUSE-DMTF", "SUSE-DMTF+", "SUSE-Docbook-XSL", "SUSE-Docbook-XSL+", "SUSE-EULA", "SUSE-EULA+", "SUSE-Egenix-1.1.0", "SUSE-Egenix-1.1.0+", "SUSE-FHS", "SUSE-FHS+", "SUSE-FLTK", "SUSE-FLTK+", "SUSE-Firmware", "SUSE-Firmware+", "SUSE-Free-Art-1.3", "SUSE-Free-Art-1.3+", "SUSE-Freetype", "SUSE-Freetype+", "SUSE-Freeware", "SUSE-Freeware+", "SUSE-GL2PS-2.0", "SUSE-GL2PS-2.0+", "SUSE-GPL-2.0+-with-openssl-exception", "SUSE-GPL-2.0+-with-openssl-exception+", "SUSE-GPL-2.0+-with-sane-exception", "SUSE-GPL-2.0+-with-sane-exception+", "SUSE-GPL-2.0-with-FLOSS-exception", "SUSE-GPL-2.0-with-FLOSS-exception+", "SUSE-GPL-2.0-with-OSI-exception", "SUSE-GPL-2.0-with-OSI-exception+", "SUSE-GPL-2.0-with-linking-exception", "SUSE-GPL-2.0-with-linking-exception+", "SUSE-GPL-2.0-with-openssl-exception", "SUSE-GPL-2.0-with-openssl-exception+", "SUSE-GPL-2.0-with-plugin-exception", "SUSE-GPL-2.0-with-plugin-exception+", "SUSE-GPL-3.0+-with-font-exception", "SUSE-GPL-3.0+-with-font-exception+", "SUSE-GPL-3.0+-with-openssl-exception", "SUSE-GPL-3.0+-with-openssl-exception+", "SUSE-GPL-3.0-with-FLOSS-exception", "SUSE-GPL-3.0-with-FLOSS-exception+", "SUSE-GPL-3.0-with-font-exception", "SUSE-GPL-3.0-with-font-exception+", "SUSE-GPL-3.0-with-openssl-exception", "SUSE-GPL-3.0-with-openssl-exception+", "SUSE-GPL-3.0-with-template-exception", "SUSE-GPL-3.0-with-template-exception+", "SUSE-Gitslave", "SUSE-Gitslave+", "SUSE-Gnuplot", "SUSE-Gnuplot+", "SUSE-Hack-Open-Font-2.0", "SUSE-Hack-Open-Font-2.0+", "SUSE-IBPL-1.0", "SUSE-IBPL-1.0+", "SUSE-IDPL-1.0", "SUSE-IDPL-1.0+", "SUSE-IEEE", "SUSE-IEEE+", "SUSE-Innernet-2.0", "SUSE-Innernet-2.0+", "SUSE-Innernet-2.00", "SUSE-Innernet-2.00+", "SUSE-LDPL-2.0", "SUSE-LDPL-2.0+", "SUSE-LGPL-2.0-with-linking-exception", "SUSE-LGPL-2.0-with-linking-exception+", "SUSE-LGPL-2.1-with-digia-exception-1.1", "SUSE-LGPL-2.1-with-digia-exception-1.1+", "SUSE-LGPL-2.1-with-nokia-exception-1.1", "SUSE-LGPL-2.1-with-nokia-exception-1.1+", "SUSE-Liberation", "SUSE-Liberation+", "SUSE-MIT-Khronos", "SUSE-MIT-Khronos+", "SUSE-Manpages", "SUSE-Manpages+", "SUSE-Matplotlib", "SUSE-Matplotlib+", "SUSE-MgOpen", "SUSE-MgOpen+", "SUSE-Oasis-Specification-Notice", "SUSE-Oasis-Specification-Notice+", "SUSE-OldFSFDocLicense", "SUSE-OldFSFDocLicense+", "SUSE-OpenPublication-1.0", "SUSE-OpenPublication-1.0+", "SUSE-PHP-2.02", "SUSE-PHP-2.02+", "SUSE-Permissive", "SUSE-Permissive+", "SUSE-Permissive-Modify-By-Patch", "SUSE-Permissive-Modify-By-Patch+", "SUSE-Public-Domain", "SUSE-Public-Domain+", "SUSE-Python-1.6", "SUSE-Python-1.6+", "SUSE-QWT-1.0", "SUSE-QWT-1.0+", "SUSE-Redistributable-Content", "SUSE-Redistributable-Content+", "SUSE-Repoze", "SUSE-Repoze+", "SUSE-SGI-FreeB-2.0", "SUSE-SGI-FreeB-2.0+", "SUSE-SIP", "SUSE-SIP+", "SUSE-SLIB", "SUSE-SLIB+", "SUSE-SNIA-1.0", "SUSE-SNIA-1.0+", "SUSE-SNIA-1.1", "SUSE-SNIA-1.1+", "SUSE-Scrot", "SUSE-Scrot+", "SUSE-Sun-Laboratories", "SUSE-Sun-Laboratories+", "SUSE-TGPPL-1.0", "SUSE-TGPPL-1.0+", "SUSE-TeX", "SUSE-TeX+", "SUSE-Ubuntu-Font-License-1.0", "SUSE-Ubuntu-Font-License-1.0+", "SUSE-XDebug", "SUSE-XDebug+", "SUSE-XFree86-with-font-exception", "SUSE-XFree86-with-font-exception+", "SUSE-XSL-Lint", "SUSE-XSL-Lint+", "SUSE-Xano", "SUSE-Xano+", "SUSE-Xenonsoft-1.00", "SUSE-Xenonsoft-1.00+", "SUSE-mirror", "SUSE-mirror+", "SUSE-mplus", "SUSE-mplus+", "SUSE-wxWidgets-3.1", "SUSE-wxWidgets-3.1+", "SWL", "SWL+", "Saxpath", "Saxpath+", "Sendmail", "Sendmail+", "Sendmail-8.23", "Sendmail-8.23+", "SimPL-2.0", "SimPL-2.0+", "Sleepycat", "Sleepycat+", "Spencer-86", "Spencer-86+", "Spencer-94", "Spencer-94+", "Spencer-99", "Spencer-99+", "SugarCRM-1.1.3", "SugarCRM-1.1.3+", "TAPR-OHL-1.0", "TAPR-OHL-1.0+", "TCL", "TCL+", "TCP-wrappers", "TCP-wrappers+", "TMate", "TMate+", "TORQUE-1.1", "TORQUE-1.1+", "TOSL", "TOSL+", "TU-Berlin-1.0", "TU-Berlin-1.0+", "TU-Berlin-2.0", "TU-Berlin-2.0+", "UCL-1.0", "UCL-1.0+", "UPL-1.0", "UPL-1.0+", "Unicode", "Unicode+", "Unicode-DFS-2015", "Unicode-DFS-2015+", "Unicode-DFS-2016", "Unicode-DFS-2016+", "Unicode-TOU", "Unicode-TOU+", "Unlicense", "Unlicense+", "VOSTROM", "VOSTROM+", "VSL-1.0", "VSL-1.0+", "Vim", "Vim+", "W3C", "W3C+", "W3C-19980720", "W3C-19980720+", "W3C-20150513", "W3C-20150513+", "WTFPL", "WTFPL+", "Watcom-1.0", "Watcom-1.0+", "Wsuipa", "Wsuipa+", "X11", "X11+", "XFree86-1.1", "XFree86-1.1+", "XSkat", "XSkat+", "Xerox", "Xerox+", "Xnet", "Xnet+", "YPL-1.0", "YPL-1.0+", "YPL-1.1", "YPL-1.1+", "ZPL-1.1", "ZPL-1.1+", "ZPL-2.0", "ZPL-2.0+", "ZPL-2.1", "ZPL-2.1+", "Zed", "Zed+", "Zend-2.0", "Zend-2.0+", "Zimbra-1.3", "Zimbra-1.3+", "Zimbra-1.4", "Zimbra-1.4+", "Zlib", "Zlib+", "blessing", "blessing+", "bzip2-1.0.5", "bzip2-1.0.5+", "bzip2-1.0.6", "bzip2-1.0.6+", "copyleft-next-0.3.0", "copyleft-next-0.3.0+", "copyleft-next-0.3.1", "copyleft-next-0.3.1+", "curl", "curl+", "diffmark", "diffmark+", "dvipdfm", "dvipdfm+", "eGenix", "eGenix+", "etalab-2.0", "etalab-2.0+", "gSOAP-1.3b", "gSOAP-1.3b+", "gnuplot", "gnuplot+", "iMatix", "iMatix+", "libpng-2.0", "libpng-2.0+", "libselinux-1.0", "libselinux-1.0+", "libtiff", "libtiff+", "mpich2", "mpich2+", "psfrag", "psfrag+", "psutils", "psutils+", "xinetd", "xinetd+", "xpp", "xpp+", "zlib-acknowledgement", "zlib-acknowledgement+", # SUSE EXCEPTIONS "AGPL-3.0", "AGPL-3.0+", "GFDL-1.1", "GFDL-1.1+", "GFDL-1.2", "GFDL-1.2+", "GFDL-1.3", "GFDL-1.3+", "GPL-3.0-with-GCC-exception", "GPL-2.0-with-classpath-exception", "GPL-2.0-with-font-exception", "SUSE-LGPL-2.1+-with-GCC-exception", "SUSE-NonFree", "GPL-1.0+", "GPL-1.0", "GPL-2.0+", "GPL-2.0", "GPL-3.0+", "GPL-3.0", "LGPL-2.0", "LGPL-2.0+", "LGPL-2.1+", "LGPL-2.1", "LGPL-3.0+", "LGPL-3.0", ] rpmlint-2.2.0+ds1/configs/openSUSE/opensuse.toml000066400000000000000000000214351415540642600215260ustar00rootroot00000000000000# Configuration for the rpmlint utility. # Configuration options used by the checks CompressExtension = "" UseVarLockSubsys = false UseVersionInChangelog = false BadnessThreshold = 999 # Enabled checks for the rpmlint to be run (besides the default set) Checks = [ "BashismsCheck", "PAMModulesCheck", "TmpFilesCheck", "SysVInitOnSystemdCheck", "SharedLibraryPolicyCheck", ] Filters = [ # Stuff autobuild takes care about '.*invalid-version.*', '.*invalid-packager.*', '.*not-standard-release-extension.*', '.*invalid-buildhost.*', '.*executable-in-library-package.*', '.*non-versioned-file-in-library-package.*', '.*shlib-policy-name-error.*', '.*hardcoded-path-in-buildroot-tag.*', '.*no-buildroot-tag.*', '.*cross-directory-hard-link.*', # Do not validate package rpm groups '.*devel-package-with-non-devel-group.*', '.*no-group-tag.*', '.*non-standard-group.*', # Output filters '.*spurious-bracket-in-.*', '.*one-line-command-in-.*', ' dir-or-file-in-opt ', # handled by CheckFilelist.py ' dir-or-file-in-usr-local ', # handled by CheckFilelist.py ' non-standard-dir-in-usr ', # handled by CheckFilelist.py 'incoherent-version-in-changelog', ' no-signature', ' symlink-crontab-file', #bnc591431 ' without-chkconfig', 'unstripped-binary-or-object.*\.ko', ' no-chkconfig', ' subsys-not-used', ' dangerous-command.*', ' setuid-binary.*', 'subdir-in-bin /sbin/conf.d/', '.* nss_db non-standard-dir-in-var db', 'non-standard-dir-in-usr openwin', 'ibcs2 non-standard-dir-in-usr i486-sysv4', 'shlibs5 non-standard-dir-in-usr i486-linux-libc5', 'explicit-lib-dependency libtool', # Filesystem package needs special exceptions '^filesystem\..*: dir-or-file-in-var-run', '^filesystem\..*: dir-or-file-in-var-lock', '^filesystem\..*: dir-or-file-in-var-tmp', '^filesystem\..*: dir-or-file-in-var-run', '^filesystem\..*: dir-or-file-in-var-lock', '^filesystem\..*: dir-or-file-in-usr-tmp', '^filesystem\..*: dir-or-file-in-tmp', '^filesystem\..*: dir-or-file-in-mnt', '^filesystem\..*: dir-or-file-in-home', '^filesystem\..*: hidden-file-or-dir /root/.gnupg', '^filesystem\..*: hidden-file-or-dir /root/.gnupg', '^filesystem\..*: hidden-file-or-dir /etc/skel/.config', '^filesystem\..*: hidden-file-or-dir /etc/skel/.local', '^filesystem\..*: hidden-file-or-dir /tmp/.X11-unix', '^filesystem\..*: hidden-file-or-dir /tmp/.ICE-unix', '^filesystem\..*: hidden-file-or-dir /etc/skel/.fonts', '^filesystem\..*: filelist-forbidden-fhs23', '^filesystem\..*: filelist-forbidden-opt', '^filesystem\..*: non-standard-uid /var/lib/nobody nobody', '^filesystem\..*: missing-dependency-to-cron', # has arch specific dirs in /usr '^filesystem\..*: no-binary', # Suppress any errors about internal packages '^qa\S+: [EWI]:', '^\S*(?:INTERNAL|internal)\.\S+: [EWI]:', # Exceptions for devel-files 'devel-file-in-non-devel-package.*/boot/vmlinuz-.*autoconf.h', 'devel-file-in-non-devel-package.*/usr/src/linux-', 'devel-file-in-non-devel-package.*/usr/share/systemtap', '-(?:examples|doc)\.\S+: \w: devel-file-in-non-devel-package', 'java\S+-demo\.\S+: \w: devel-file-in-non-devel-package', 'avr-libc\.\S+: \w: devel-file-in-non-devel-package', 'cross-.*devel-file-in-non-devel-package', 'cmake.*devel-file-in-non-devel-package', 'gcc\d\d.*devel-file-in-non-devel-package', 'OpenOffice_org-sdk\.\S+: \w: devel-file-in-non-devel-package', 'wnn-sdk\.\S+: \w: devel-file-in-non-devel-package', 'ocaml\.\S+: \w: devel-file-in-non-devel-package', 'xorg-x11-server-sdk\.\S+: \w: devel-file-in-non-devel-package', 'linux-kernel-headers\.\S+: \w: devel-file-in-non-devel-package', ' devel-file-in-non-devel-package.*-config', 'libtool\.\S+: \w: devel-file-in-non-devel-package', 'sdb.* dangling-relative-symlink /usr/share/doc/sdb/.*/gifs ../gifs', 'kernel-modules-not-in-kernel-packages', # SUSE kmp's don't need manual depmod (bnc#456048) 'module-without-depmod-postin', 'postin-with-wrong-depmod', 'module-without-depmod-postun', 'postun-with-wrong-depmod', 'configure-without-libdir-spec', 'conffile-without-noreplace-flag /etc/init.d', 'use-of-RPM_SOURCE_DIR', 'use-tmp-in-', 'symlink-contains-up-and-down-segments /var/lib/named', 'no-ldconfig-symlink', 'aaa_base\.\S+: \w: use-of-home-in-%post', 'description-line-too-long', 'hardcoded-library-path', # Doesn't seem to make sense 'invalid-ldconfig-symlink', 'invalid-soname', 'library-not-linked-against-libc', 'only-non-binary-in-usr-lib', 'outside-libdir-files', # We want these files ' perl-temp-file ', ' hidden-file-or-dir .*/\.packlist', ' hidden-file-or-dir .*/\.directory', 'perl-.*no-binary', ' no-major-in-name ', # We check for that already 'dangling-relative-symlink', ' lib-package-without-%mklibname', ' requires-on-release', ' non-executable-script /etc/profile.d/', ' non-executable-script /var/adm/fillup-templates/', ' init-script-name-with-dot ', '.* statically-linked-binary /sbin/ldconfig', '.* statically-linked-binary /sbin/init', 'valgrind.* statically-linked-binary', 'ldconfig-post.*/ddiwrapper/wine/', 'glibc\.\S+: \w: statically-linked-binary /usr/sbin/glibc_post_upgrade', ' symlink-should-be-relative ', ' binary-or-shlib-defines-rpath .*ORIGIN', 'libzypp.*shlib-policy-name-error.*libzypp', 'libtool.*shlib-policy.*', # Stuff that is currently too noisy, but might become relevant in the future ' prereq-use', ' file-not-utf8', ' tag-not-utf8', ' setup-not-quiet', ' no-cleaning-of-buildroot ', ' mixed-use-of-spaces-and-tabs ', ' prereq-use ', # An issue with OBS, works with autobuild ' no-packager-tag', ' unversioned-explicit-provides ', ' unversioned-explicit-obsoletes ', ' service-default-enabled ', ' non-standard-dir-perm ', ' conffile-without-noreplace-flag ', ' non-standard-executable-perm ', ' jar-not-indexed ', ' uncompressed-zip ', ' %ifarch-applied-patch ', ' read-error ', ' init-script-without-chkconfig-postin ', ' init-script-without-chkconfig-preun ', ' postin-without-chkconfig ', ' preun-without-chkconfig ', ' no-dependency-on locales', ' shlib-policy-name-error', ' binary-or-shlib-defines-rpath', ' executable-marked-as-config-file', ' log-files-without-logrotate', ' hardcoded-prefix-tag', ' no-documentation', ' multiple-specfiles', ' no-default-runlevel ', ' setgid-binary ', ' non-readable ', ' postin-without-ghost-file-creation ', # Exceptions for filelist checks 'nfs-client\.\S+: \w: filelist-forbidden-backup-file /var/lib/nfs/sm.bak', 'perl\.\S+: \w: filelist-forbidden-perl-dir ', 'info\.\S+: \w: info-dir-file .*/usr/share/info/dir', # These packages are used for CD creation and are not supposed to be # installed. It's still a dirty hack to make an exception. The # packages should either be built in a separate project with # different config or file be put somewhere below /opt/suse/* '(?:dosutils|skelcd|installation-images|yast2-slide-show|instlux|skelcd-.*|patterns-.*)\.\S+: \w: filelist-forbidden-fhs23 /CD1', # Too noisy, and usually not something downstream packagers can fix ' incorrect-fsf-address ', ' no-manual-page-for-binary ', ' static-library-without-debuginfo /usr/lib(?:64)?/ghc-[\d\.]+/', # Many places have shorter paths ' non-coherent-filename ', # Mandriva specific stuff that we don't want ' invalid-build-requires ', ' no-provides ', # Bash completion files are not scripts, do not require them marked as %config 'W: non-conffile-in-etc /etc/bash_completion.d/', # Info uses file triggers now (boo#1152169) ' info-files-without-install-info-postin' , ' postin-without-install-info ', ' info-files-without-install-info-postun ', ] [DanglingSymlinkExceptions."/usr/share/doc/licenses/"] path = "/usr/share/doc/licenses/" name = "licenses" [DanglingSymlinkExceptions."consolehelper$"] path = "consolehelper$" name = "usermode-consoleonly" [Descriptions] non-standard-uid = '''A file in this package is owned by an unregistered user id. To register the user, please make a pull request to the rpmlint config file configs/openSUSE/users-groups.toml in the rpmlint repository. ''' non-standard-gid = '''A file in this package is owned by an unregistered group id. To register the group, please make a pull request to the rpmlint config file configs/openSUSE/users-groups.toml in the rpmlint repository. ''' no-changelogname-tag = '''There is no changelog. Please insert a '%changelog' section heading in your spec file and prepare your changes file using e.g. the 'osc vc' command.''' rpmlint-2.2.0+ds1/configs/openSUSE/pam-modules.toml000066400000000000000000000063131415540642600221060ustar00rootroot00000000000000PAMAuthorizedModules = [ # pam_krb5 "pam_krb5.so", "pam_krb5afs.so", # ecryptfs-utils "pam_ecryptfs.so", # gnome-keyring-pam "pam_gnome_keyring.so", # samba-winbind "pam_winbind.so", # pam_ssh "pam_ssh.so", # pam_mount "pam_mount.so", # pam_ccreds "pam_ccreds.so", # pam_radius "pam_radius_auth.so", # pam_pkcs11 "pam_pkcs11.so", # nss-pam-ldapd "pam_ldap.so", # pam_passwdqc "pam_passwdqc.so", # pam_userpass "pam_userpass.so", # pam_apparmor "pam_apparmor.so", # pam_ldap "pam_ldap.so", # opie "pam_opie.so", # pam "pam_access.so", "pam_cracklib.so", "pam_debug.so", "pam_deny.so", "pam_echo.so", "pam_env.so", "pam_exec.so", "pam_faildelay.so", "pam_filter.so", "pam_ftp.so", "pam_group.so", "pam_issue.so", "pam_keyinit.so", "pam_lastlog.so", "pam_limits.so", "pam_listfile.so", "pam_localuser.so", "pam_loginuid.so", "pam_mail.so", "pam_mkhomedir.so", "pam_motd.so", "pam_namespace.so", "pam_nologin.so", "pam_permit.so", "pam_pwhistory.so", "pam_rhosts.so", "pam_rootok.so", "pam_securetty.so", "pam_selinux.so", "pam_sepermit.so", "pam_shells.so", "pam_stress.so", "pam_succeed_if.so", "pam_tally2.so", "pam_time.so", "pam_timestamp.so", "pam_tty_audit.so", "pam_umask.so", "pam_unix.so", "pam_unix_acct.so", "pam_unix_auth.so", "pam_unix_passwd.so", "pam_unix_session.so", "pam_userdb.so", "pam_warn.so", "pam_wheel.so", "pam_xauth.so", # systemd "pam_systemd.so", # sssd "pam_sss.so", # pam_mktemp "pam_mktemp.so", # pam_csync "pam_csync.so", # pam_chroot "pam_chroot.so", # pam_snapper (bnc#815383) "pam_snapper.so", # pam_gdm (bsc#1004346) "pam_gdm.so", # pam_slurm (bsc#1007053) "pam_slurm.so", # pam_slurm_adopt (bsc#1116758) "pam_slurm_adopt.so", # pam_script (bsc#1039848) "pam_script.so", # pam_yubico (bsc#1087060) "pam_yubico.so", # pam_oath (bsc#1089114) "pam_oath.so", # pam_p11 (bsc#1123916) "pam_p11.so", # pam_cifscreds (bsc#1150527) "pam_cifscreds.so", # libpwquality (bsc#1150520) "pam_pwquality.so", # lxc (bsc#1150519) "pam_cgfs.so", # google-authenticator-libpam (bsc#1150524) - potential future removal candidate "pam_google_authenticator.so", # pam_u2f (bsc#1087061) "pam_u2f.so", # pam_kwallet (bsc#993806) "pam_kwallet5.so", # pam_dbus (bsc#1039709) "pam_dbus.so", # google-compute-engine (bsc#1146353) "pam_oslogin_admin.so", "pam_oslogin_login.so", # fprintd (bsc#792095) "pam_fprintd.so", # mariadb (bsc#1163362) "pam_user_map.so", # oddjob (bsc#1169494) "pam_oddjob_mkhomedir.so", # cockpit (bsc#1169614) "pam_cockpit_cert.so", "pam_ssh_add.so", # pam (bsc#1171564) "pam_usertype.so", # pam (bsc#1171563) "pam_setquota.so", # kanidm (bsc#1173387) "pam_kanidm.so", # GNOME malcontent parental control (bsc#1177974) "pam_malcontent.so", # pam (bsc#1171562) "pam_faillock.so" ] rpmlint-2.2.0+ds1/configs/openSUSE/pie-executables.toml000066400000000000000000000132561415540642600227460ustar00rootroot00000000000000PieExecutables = [ "/bin/ping", "/usr/bin/ping", "/bin/ping6", "/usr/bin/ping6", "/bin/su", "/usr/bin/su", "/usr/bin/pidgin", "/sbin/arping", "/sbin/clockdiff", "/sbin/dhclient", "/sbin/dhcpcd", "/sbin/klogd", "/sbin/rpcbind", "/sbin/syslogd", "/sbin/tracepath", "/sbin/tracepath6", "/usr/bin/uniconv", "/usr/bin/achfile", "/usr/bin/adv1tov2", "/usr/bin/aecho", "/usr/bin/afile", "/usr/bin/afppasswd", "/usr/bin/at", "/usr/bin/cadaver", "/usr/bin/chage", "/usr/bin/chfn", "/usr/bin/chsh", "/usr/bin/ciptool", "/usr/bin/cnid_index", "/usr/bin/dig", "/usr/bin/dund", "/usr/bin/expiry", "/usr/bin/finger", "/usr/bin/getzones", "/usr/bin/gpasswd", "/usr/bin/gpg", "/usr/bin/gpgsplit", "/usr/bin/gpgv", "/usr/bin/hcitool", "/usr/bin/hidd", "/usr/bin/host", "/usr/bin/htpasswd", "/usr/bin/l2ping", "/usr/bin/lppasswd", "/usr/bin/megatron", "/usr/bin/nbplkup", "/usr/bin/nbprgstr", "/usr/bin/nbpunrgstr", "/usr/bin/ncplogin", "/usr/bin/ncpmap", "/usr/bin/net", "/usr/bin/newgrp", "/usr/bin/nmblookup", "/usr/bin/nslookup", "/usr/bin/nsupdate", "/usr/bin/nwsfind", "/usr/bin/omshell", "/usr/bin/pand", "/usr/bin/pap", "/usr/bin/papstatus", "/usr/bin/passwd", "/usr/bin/pdbedit", "/usr/bin/profiles", "/usr/bin/psorder", "/usr/bin/rcp", "/usr/bin/rexec", "/usr/bin/rfcomm", "/usr/bin/rlogin", "/usr/bin/rpcclient", "/usr/bin/rsh", "/usr/bin/scp", "/usr/bin/sdptool", "/usr/bin/sftp", "/usr/bin/showppd", "/usr/bin/smbcacls", "/usr/bin/smbclient", "/usr/bin/smbcontrol", "/usr/bin/smbcquotas", "/sbin/mount.cifs", "/usr/bin/smbpasswd", "/usr/bin/smbspool", "/usr/bin/smbstatus", "/usr/bin/smbtree", "/usr/bin/ssh", "/usr/bin/ssh-add", "/usr/bin/ssh-agent", "/usr/bin/ssh-keygen", "/usr/bin/ssh-keyscan", "/usr/bin/svn", "/usr/bin/svnadmin", "/usr/bin/svndumpfilter", "/usr/bin/svnlook", "/usr/bin/svnserve", "/usr/bin/svnversion", "/usr/bin/talk", "/usr/bin/telnet", "/usr/bin/testparm", "/usr/bin/testprns", "/usr/bin/timeout", "/usr/bin/wbinfo", "/usr/lib/mit/bin/ftp", "/usr/lib/mit/bin/gss-client", "/usr/lib/mit/bin/kdestroy", "/usr/lib/mit/bin/kinit", "/usr/lib/mit/bin/klist", "/usr/lib/mit/bin/kpasswd", "/usr/lib/mit/bin/krb524init", "/usr/lib/mit/bin/ksu", "/usr/lib/mit/bin/kvno", "/usr/lib/mit/bin/rcp", "/usr/lib/mit/bin/rlogin", "/usr/lib/mit/bin/rsh", "/usr/lib/mit/bin/sclient", "/usr/lib/mit/bin/sim_client", "/usr/lib/mit/bin/telnet", "/usr/lib/mit/bin/uuclient", "/usr/lib/mit/bin/v4rcp", "/usr/lib/mit/sbin/ftpd", "/usr/lib/mit/sbin/gss-server", "/usr/lib/mit/sbin/kadmin", "/usr/lib/mit/sbin/kadmin.local", "/usr/lib/mit/sbin/kadmind", "/usr/lib/mit/sbin/kdb5_util", "/usr/lib/mit/sbin/klogind", "/usr/lib/mit/sbin/kprop", "/usr/lib/mit/sbin/kpropd", "/usr/lib/mit/sbin/krb524d", "/usr/lib/mit/sbin/krb5kdc", "/usr/lib/mit/sbin/kshd", "/usr/lib/mit/sbin/ktutil", "/usr/lib/mit/sbin/login.krb5", "/usr/lib/mit/sbin/sim_server", "/usr/lib/mit/sbin/sserver", "/usr/lib/mit/sbin/telnetd", "/usr/lib/mit/sbin/uuserver", "/usr/lib/news/bin/innd", "/usr/lib/news/bin/innbind", "/usr/lib/news/bin/rnews", "/usr/sbin/afpd", "/usr/sbin/amcheck", "/usr/sbin/amdd", "/usr/sbin/atalkd", "/usr/sbin/atd", "/usr/sbin/automount", "/usr/sbin/chat", "/usr/sbin/cnid_dbd", "/usr/sbin/cnid_metad", "/usr/sbin/cron", "/usr/sbin/cupsd", "/usr/sbin/dhcpd", "/usr/sbin/dhcrelay", "/usr/sbin/dnssec-keygen", "/usr/sbin/dnssec-signzone", "/usr/sbin/exim", "/usr/sbin/hciattach", "/usr/sbin/bluetoothd", "/usr/sbin/hciconfig", "/usr/sbin/hid2hci", "/usr/sbin/httpd2", "/usr/sbin/httpd2-prefork", "/usr/sbin/httpd2-worker", "/usr/sbin/in.fingerd", "/usr/sbin/in.ntalkd", "/usr/sbin/in.rexecd", "/usr/sbin/in.rlogind", "/usr/sbin/in.rshd", "/usr/sbin/in.telnetd", "/usr/sbin/irqbalance", "/usr/sbin/lwresd", "/usr/sbin/mailstats", "/usr/sbin/makemap", "/usr/sbin/named", "/usr/sbin/named-checkconf", "/usr/sbin/named-checkzone", "/usr/sbin/nmbd", "/usr/sbin/nscd", "/usr/sbin/ntlm_auth", "/usr/sbin/ntp-keygen", "/usr/sbin/ntpd", "/usr/sbin/ntpdc", "/usr/sbin/ntpq", "/usr/sbin/ntptime", "/usr/sbin/openvpn", "/usr/sbin/papd", "/usr/sbin/postfix", "/usr/sbin/pppd", "/usr/sbin/praliases", "/usr/sbin/radiusd", "/usr/sbin/rarpd", "/usr/sbin/rndc", "/usr/sbin/rndc-confgen", "/usr/sbin/rotatelogs2", "/usr/sbin/rpc.mountd", "/usr/sbin/rpc.nfsd", "/usr/sbin/rpc.rquotad", "/usr/sbin/rpc.rwalld", "/usr/sbin/rpc.yppasswdd", "/usr/sbin/rpc.ypxfrd", "/usr/sbin/safe_finger", "/usr/sbin/sendmail", "/usr/lib/sudo/sesh", "/usr/lib/openldap/slapd", "/usr/sbin/smartctl", "/usr/sbin/smartd", "/usr/sbin/smbd", "/usr/sbin/snmpd", "/usr/sbin/snmptrapd", "/usr/sbin/squid", "/usr/sbin/squidclient", "/usr/sbin/sshd", "/usr/sbin/stunnel", "/usr/sbin/suexec2", "/usr/sbin/tcpd", "/usr/sbin/tickadj", "/usr/sbin/traceroute", "/usr/sbin/traceroute6", "/usr/sbin/try-from", "/usr/sbin/utempter", "/usr/sbin/visudo", "/usr/sbin/vsftpd", "/usr/sbin/winbindd", "/usr/sbin/xinetd", "/usr/sbin/yppush", "/usr/sbin/ypserv", "/usr/bin/zone2ldap", ] rpmlint-2.2.0+ds1/configs/openSUSE/scoring.toml000066400000000000000000000020661415540642600213300ustar00rootroot00000000000000[Scoring] arch-dependent-file-in-usr-share = 590 arch-independent-package-contains-binary-or-object = 499 binary-in-etc = 900 devel-file-in-non-devel-package = 50 dir-or-file-in-var-lock = 10000 dir-or-file-in-var-run = 10000 env-script-interpreter = 9 executable-docs = 900 file-contains-buildroot = 10000 files-duplicated-waste = 100 hardlink-across-config-files = 10000 hardlink-across-partition = 10000 info-dir-file = 10000 invalid-license = 100000 invalid-pkgconfig-file = 10000 libtool-wrapper-in-package = 10000 lto-bytecode = 10000 lto-no-text-in-archive = 10000 makefile-junk = 109 no-pkg-config-provides = 300 non-ghost-in-run = 10000 non-position-independent-executable = 10000 percent-in-conflicts = 10000 percent-in-dependency = 10000 percent-in-obsoletes = 10000 percent-in-provides = 10000 spurious-executable-perm = 50 summary-ended-with-dot = 20 summary-not-capitalized = 20 summary-too-long = 200 pam-unauthorized-module = 10000 wrong-script-interpreter = 490 obsolete-insserv-requirement = 10000 deprecated-init-script = 10000 deprecated-boot-script = 10000 rpmlint-2.2.0+ds1/configs/openSUSE/users-groups.toml000066400000000000000000000140011415540642600223320ustar00rootroot00000000000000StandardGroups = [ 'aegis', 'antivir', 'arangodb', 'at', 'audio', 'avahi', 'bacula', 'beagleindex', 'bigsister', 'bin', 'bird', 'bitcoin', 'bitlbee', 'boinc', 'casaauth', 'cdrom', 'ceilometer', 'ceph', 'cephadm', 'chef', 'chrony', 'cinder', 'citadel', 'colord', 'conman', 'console', 'coroqnetd', 'coturn', 'crowbar', 'cwbconv', '_cscreen', 'daapd', 'daemon', 'davfs2', 'dba', 'ddclient', 'debuginfod', 'dehydrated', 'dialout', 'disk', 'distcc', 'dnscrypt', 'dosemu', 'dovecot', 'elasticsearch', 'epmd', 'festival', 'ffums', 'firebird', 'firejail', 'floppy', 'fonehome', 'ftp', 'games', 'gdm', 'gerbera', 'geronimo', 'glance', '_gns3', 'grafana', 'guixbuild', 'haclient', 'haldaemon', 'heat', 'hsqldb', 'icecast', 'icecream', 'icinga', 'icingacmd', 'ifdrwww', 'intermezzo', 'iouyap', 'jboss', 'jenkins', 'jetty5', 'jitsi', 'jonas', 'keystone', 'kmem', 'kolab', 'kolab-n', 'kolab-r', 'kvm', 'ldap', 'libstoragemgmt', 'libvirt', 'lightdm', 'lighttpd', 'localham', 'locate', 'logstash', 'lp', 'lxdm', 'mail', 'maildrop', 'mailman', 'man', 'mdom', 'memcached', 'messagebus', 'minetest', 'mktex', 'modem', 'mumble-server', 'munge', 'mysql', 'nagcmd', 'nagios', 'named', 'neutron', 'news', 'nginx', 'nobody', 'nogroup', 'nova', 'novell_nogroup', 'novlxtier', '_nsd', 'ntadmin', 'ntop', 'ntp', 'oinstall', 'opensearch', 'openvswitch', 'orthanc', 'otrs', 'ovirtagent', 'pcp', 'pcpqa', 'pdns', 'pegasus', 'pkcs11', 'polipo', 'polkituser', 'postfix', 'postgres', 'pound', 'powersave', 'privoxy', 'prometheus', 'prosody', 'ptokax', 'public', 'pulse', 'pulse-access', 'pulse-rt', 'puppet', 'qemu', 'quagga', 'quasselcore', 'rabbitmq', 'radicale', 'radiusd', 'root', 'sabayon-admin', 'salt', 'sanlock', 'sapdb', 'sddm', 'sensu', 'shadow', 'shibd', 'singularity', 'siproxd', '_sks', 'slurm', 'snort', 'sogo', 'squid', 'sshd', 'suse-ncc', 'svn', 'swift', 'synapse', 'sys', 'systemd-journal', 'systemd-journal-gateway', 'tape', 'tftp', 'tomcat', 'tomcat4', 'tor', 'tox', 'trove', 'trusted', 'tryton', 'tss', 'ts-shell', 'tty', 'unbound', 'users', 'utmp', 'uucp', 'uuidd', 'vacation', 'varnish', 'video', 'vnc', 'vscan', 'warewulf', 'wheel', 'wireshark', 'www', 'xok', 'xrootd', 'xymon', 'zabbix', 'zabbixs', 'zeroinst', 'zkeyadm', 'znc', 'zope', ] StandardUsers = [ 'aegis', 'amanda', 'aodh', 'arangodb', 'asterisk', 'at', 'avahi', 'bacula', 'barbican', 'beagleindex', 'bigsister', 'bin', 'bird', 'bitcoin', 'bitlbee', 'boinc', 'casaatsd', 'casaatvd', 'casaauth', 'ceilometer', 'ceph', 'cephadm', 'chef', 'chrony', 'cinder', 'citadel', 'cntlm', 'colord', 'conman', 'cop', 'coroqnetd', 'coturn', 'crowbar', 'cyrus', '_cscreen', 'daapd', 'daemon', 'davfs2', 'ddclient', 'dehydrated', 'designate', 'dhcpd', 'debuginfod', 'distcc', 'dnscrypt', 'dovecot', 'dpbox', 'dvbdaemon', 'ec2-api', 'elasticsearch', 'epmd', 'fax', 'festival', 'fetchmail', 'ffums', 'firebird', 'fonehome', 'ftp', 'games', 'gdm', 'gerbera', 'geronimo', 'glance', 'gnats', 'gnocchi', '_gns3', 'gnump3d', 'grafana', 'hacluster', 'haldaemon', 'heat', 'hsqldb', 'icecast', 'icecream', 'icinga', 'intermezzo', 'iodined', 'irc', 'ironic', 'jabber', 'jboss', 'jenkins', 'jetty5', 'jibri', 'jicofo', 'jonas', 'jvb', 'keystone', 'kolab', 'kolab-n', 'kolab-r', 'ldap', 'libstoragemgmt', 'lightdm', 'lighttpd', 'logstash', 'lp', 'lxdm', 'magnum', 'mail', 'mailman', 'man', 'manila', 'mdnsd', 'mdom', 'mednafen', 'memcached', 'messagebus', 'minetest', 'mktex', 'mpd', 'mumble-server', 'munge', 'murano', 'mysql', 'nagios', 'named', 'neutron', 'news', 'nginx', 'nova', 'novell_nobody', 'novlifdr', 'novlxregd', 'novlxsrvd', '_nsd', 'ntop', 'ntp', 'octavia', 'opensearch', 'openvswitch', 'oracle', 'orthanc', 'otrs', 'ovirtagent', 'partimag', 'pcp', 'pcpqa', 'pdns', 'pegasus', 'polipo', 'polkitd', 'polkituser', 'pop', 'postfix', 'postgres', 'postgrey', 'pound', 'privoxy', 'prometheus', 'prosody', 'ptokax', 'pulse', 'puppet', 'qemu', 'quagga', 'quasselcore', 'rabbitmq', 'radicale', 'radiusd', 'radvd', '_rmt', 'root', 'sabayon-admin', 'sahara', 'salt', 'sanlock', 'sapdb', 'sddm', 'sensu', 'shibd', 'siproxd', '_sks', 'slurm', 'snort', 'sogo', 'squid', 'sshd', 'statd', 'suse-ncc', 'svn', 'swift', 'synapse', 'systemd-journal-gateway', 'tftp', 'tomcat', 'tomcat4', 'tor', 'toxcmd', 'trove', 'tryton', 'tss', 'ulogd', 'unbound', 'upsd', 'uucp', 'uuidd', 'vacation', 'varnish', 'vdr', 'vnc', 'vscan', 'wnn', 'wwwrun', 'xrootd', 'xymon', 'yastws', 'zabbix', 'zabbixs', 'zaqar', 'zeroinst', 'znc', 'zope', ] rpmlint-2.2.0+ds1/conftest.py000066400000000000000000000000761415540642600160740ustar00rootroot00000000000000# Having this file, pytest will add project root to sys.path. rpmlint-2.2.0+ds1/diff.py000077500000000000000000000000711415540642600151550ustar00rootroot00000000000000#!/usr/bin/python3 from rpmlint.cli import diff diff() rpmlint-2.2.0+ds1/lint.py000077500000000000000000000000711415540642600152130ustar00rootroot00000000000000#!/usr/bin/python3 from rpmlint.cli import lint lint() rpmlint-2.2.0+ds1/pyproject.toml000066400000000000000000000001521415540642600166040ustar00rootroot00000000000000[build-system] requires = [ "setuptools>=39.2", "wheel" ] build-backend = "setuptools.build_meta" rpmlint-2.2.0+ds1/rpmlint/000077500000000000000000000000001415540642600153575ustar00rootroot00000000000000rpmlint-2.2.0+ds1/rpmlint/__init__.py000066400000000000000000000000001415540642600174560ustar00rootroot00000000000000rpmlint-2.2.0+ds1/rpmlint/__isocodes__.py000066400000000000000000001767151415540642600203560ustar00rootroot00000000000000# flake8: noqa # Generated with tools/generate-isocodes.py LANGUAGES = set( ['aa', 'aaa', 'aab', 'aac', 'aad', 'aae', 'aaf', 'aag', 'aah', 'aai', 'aak', 'aal', 'aan', 'aao', 'aap', 'aaq', 'aas', 'aat', 'aau', 'aaw', 'aax', 'aaz', 'ab', 'aba', 'abb', 'abc', 'abd', 'abe', 'abf', 'abg', 'abh', 'abi', 'abj', 'abl', 'abm', 'abn', 'abo', 'abp', 'abq', 'abr', 'abs', 'abt', 'abu', 'abv', 'abw', 'abx', 'aby', 'abz', 'aca', 'acb', 'acd', 'ace', 'acf', 'ach', 'aci', 'ack', 'acl', 'acm', 'acn', 'acp', 'acq', 'acr', 'acs', 'act', 'acu', 'acv', 'acw', 'acx', 'acy', 'acz', 'ada', 'adb', 'add', 'ade', 'adf', 'adg', 'adh', 'adi', 'adj', 'adl', 'adn', 'ado', 'adq', 'adr', 'ads', 'adt', 'adu', 'adw', 'adx', 'ady', 'adz', 'ae', 'aea', 'aeb', 'aec', 'aed', 'aee', 'aek', 'ael', 'aem', 'aen', 'aeq', 'aer', 'aes', 'aeu', 'aew', 'aey', 'aez', 'af', 'afa', 'afb', 'afd', 'afe', 'afg', 'afh', 'afi', 'afk', 'afn', 'afo', 'afp', 'afs', 'aft', 'afu', 'afz', 'aga', 'agb', 'agc', 'agd', 'age', 'agf', 'agg', 'agh', 'agi', 'agj', 'agk', 'agl', 'agm', 'agn', 'ago', 'agq', 'agr', 'ags', 'agt', 'agu', 'agv', 'agw', 'agx', 'agy', 'agz', 'aha', 'ahb', 'ahg', 'ahh', 'ahi', 'ahk', 'ahl', 'ahm', 'ahn', 'aho', 'ahp', 'ahr', 'ahs', 'aht', 'aia', 'aib', 'aic', 'aid', 'aie', 'aif', 'aig', 'aih', 'aii', 'aij', 'aik', 'ail', 'aim', 'ain', 'aio', 'aip', 'aiq', 'air', 'ais', 'ait', 'aiw', 'aix', 'aiy', 'aja', 'ajg', 'aji', 'ajn', 'ajp', 'ajt', 'aju', 'ajw', 'ajz', 'ak', 'akb', 'akc', 'akd', 'ake', 'akf', 'akg', 'akh', 'aki', 'akj', 'akk', 'akl', 'akm', 'ako', 'akp', 'akq', 'akr', 'aks', 'akt', 'aku', 'akv', 'akw', 'akx', 'aky', 'akz', 'ala', 'alc', 'ald', 'ale', 'alf', 'alg', 'alh', 'ali', 'alj', 'alk', 'all', 'alm', 'aln', 'alo', 'alp', 'alq', 'alr', 'als', 'alt', 'alu', 'alw', 'alx', 'aly', 'alz', 'am', 'ama', 'amb', 'amc', 'ame', 'amf', 'amg', 'ami', 'amj', 'amk', 'aml', 'amm', 'amn', 'amo', 'amp', 'amq', 'amr', 'ams', 'amt', 'amu', 'amv', 'amw', 'amx', 'amy', 'amz', 'an', 'ana', 'anb', 'anc', 'and', 'ane', 'anf', 'ang', 'anh', 'ani', 'anj', 'ank', 'anl', 'anm', 'ann', 'ano', 'anp', 'anq', 'anr', 'ans', 'ant', 'anu', 'anv', 'anw', 'anx', 'any', 'anz', 'aoa', 'aob', 'aoc', 'aod', 'aoe', 'aof', 'aog', 'aoh', 'aoi', 'aoj', 'aok', 'aol', 'aom', 'aon', 'aor', 'aos', 'aot', 'aou', 'aox', 'aoz', 'apa', 'apb', 'apc', 'apd', 'ape', 'apf', 'apg', 'aph', 'api', 'apj', 'apk', 'apl', 'apm', 'apn', 'apo', 'app', 'apq', 'apr', 'aps', 'apt', 'apu', 'apv', 'apw', 'apx', 'apy', 'apz', 'aqc', 'aqd', 'aqg', 'aqm', 'aqn', 'aqp', 'aqr', 'aqt', 'aqz', 'ar', 'arb', 'arc', 'ard', 'are', 'arh', 'ari', 'arj', 'ark', 'arl', 'arn', 'aro', 'arp', 'arq', 'arr', 'ars', 'art', 'aru', 'arv', 'arw', 'arx', 'ary', 'arz', 'as', 'asa', 'asb', 'asc', 'asd', 'ase', 'asf', 'asg', 'ash', 'asi', 'asj', 'ask', 'asl', 'asn', 'aso', 'asp', 'asq', 'asr', 'ass', 'ast', 'asu', 'asv', 'asw', 'asx', 'asy', 'asz', 'ata', 'atb', 'atc', 'atd', 'ate', 'atg', 'ath', 'ati', 'atj', 'atk', 'atl', 'atm', 'atn', 'ato', 'atp', 'atq', 'atr', 'ats', 'att', 'atu', 'atv', 'atw', 'atx', 'aty', 'atz', 'aua', 'aub', 'auc', 'aud', 'aug', 'auh', 'aui', 'auj', 'auk', 'aul', 'aum', 'aun', 'auo', 'aup', 'auq', 'aur', 'aus', 'aut', 'auu', 'auw', 'aux', 'auy', 'auz', 'av', 'avb', 'avd', 'avi', 'avk', 'avl', 'avm', 'avn', 'avo', 'avs', 'avt', 'avu', 'avv', 'awa', 'awb', 'awc', 'awe', 'awg', 'awh', 'awi', 'awk', 'awm', 'awn', 'awo', 'awr', 'aws', 'awt', 'awu', 'awv', 'aww', 'awx', 'awy', 'axb', 'axe', 'axg', 'axk', 'axl', 'axm', 'axx', 'ay', 'aya', 'ayb', 'ayc', 'ayd', 'aye', 'ayg', 'ayh', 'ayi', 'ayk', 'ayl', 'ayn', 'ayo', 'ayp', 'ayq', 'ayr', 'ays', 'ayt', 'ayu', 'ayy', 'ayz', 'az', 'aza', 'azb', 'azd', 'azg', 'azj', 'azm', 'azn', 'azo', 'azt', 'azz', 'ba', 'baa', 'bab', 'bac', 'bad', 'bae', 'baf', 'bag', 'bah', 'bai', 'baj', 'bal', 'ban', 'bao', 'bap', 'bar', 'bas', 'bat', 'bau', 'bav', 'baw', 'bax', 'bay', 'bba', 'bbb', 'bbc', 'bbd', 'bbe', 'bbf', 'bbg', 'bbh', 'bbi', 'bbj', 'bbk', 'bbl', 'bbm', 'bbn', 'bbo', 'bbp', 'bbq', 'bbr', 'bbs', 'bbt', 'bbu', 'bbv', 'bbw', 'bbx', 'bby', 'bbz', 'bca', 'bcb', 'bcc', 'bcd', 'bce', 'bcf', 'bcg', 'bch', 'bci', 'bcj', 'bck', 'bcl', 'bcm', 'bcn', 'bco', 'bcp', 'bcq', 'bcr', 'bcs', 'bct', 'bcu', 'bcv', 'bcw', 'bcy', 'bcz', 'bda', 'bdb', 'bdc', 'bdd', 'bde', 'bdf', 'bdg', 'bdh', 'bdi', 'bdj', 'bdk', 'bdl', 'bdm', 'bdn', 'bdo', 'bdp', 'bdq', 'bdr', 'bds', 'bdt', 'bdu', 'bdv', 'bdw', 'bdx', 'bdy', 'bdz', 'be', 'bea', 'beb', 'bec', 'bed', 'bee', 'bef', 'beg', 'beh', 'bei', 'bej', 'bek', 'bem', 'beo', 'bep', 'beq', 'ber', 'bes', 'bet', 'beu', 'bev', 'bew', 'bex', 'bey', 'bez', 'bfa', 'bfb', 'bfc', 'bfd', 'bfe', 'bff', 'bfg', 'bfh', 'bfi', 'bfj', 'bfk', 'bfl', 'bfm', 'bfn', 'bfo', 'bfp', 'bfq', 'bfr', 'bfs', 'bft', 'bfu', 'bfw', 'bfx', 'bfy', 'bfz', 'bg', 'bga', 'bgb', 'bgc', 'bgd', 'bge', 'bgf', 'bgg', 'bgi', 'bgj', 'bgk', 'bgl', 'bgn', 'bgo', 'bgp', 'bgq', 'bgr', 'bgs', 'bgt', 'bgu', 'bgv', 'bgw', 'bgx', 'bgy', 'bgz', 'bh', 'bha', 'bhb', 'bhc', 'bhd', 'bhe', 'bhf', 'bhg', 'bhh', 'bhi', 'bhj', 'bhl', 'bhm', 'bhn', 'bho', 'bhp', 'bhq', 'bhr', 'bhs', 'bht', 'bhu', 'bhv', 'bhw', 'bhx', 'bhy', 'bhz', 'bi', 'bia', 'bib', 'bic', 'bid', 'bie', 'bif', 'big', 'bij', 'bik', 'bil', 'bim', 'bin', 'bio', 'bip', 'biq', 'bir', 'bit', 'biu', 'biv', 'biw', 'bix', 'biy', 'biz', 'bja', 'bjb', 'bjc', 'bje', 'bjf', 'bjg', 'bjh', 'bji', 'bjj', 'bjk', 'bjl', 'bjm', 'bjn', 'bjo', 'bjp', 'bjr', 'bjs', 'bjt', 'bju', 'bjv', 'bjw', 'bjx', 'bjy', 'bjz', 'bka', 'bkc', 'bkd', 'bkf', 'bkg', 'bkh', 'bki', 'bkj', 'bkk', 'bkl', 'bkm', 'bkn', 'bko', 'bkp', 'bkq', 'bkr', 'bks', 'bkt', 'bku', 'bkv', 'bkw', 'bkx', 'bky', 'bkz', 'bla', 'blb', 'blc', 'bld', 'ble', 'blf', 'blg', 'blh', 'bli', 'blj', 'blk', 'bll', 'blm', 'bln', 'blo', 'blp', 'blq', 'blr', 'bls', 'blt', 'blv', 'blw', 'blx', 'bly', 'blz', 'bm', 'bma', 'bmb', 'bmc', 'bmd', 'bme', 'bmf', 'bmg', 'bmh', 'bmi', 'bmj', 'bmk', 'bml', 'bmm', 'bmn', 'bmo', 'bmp', 'bmq', 'bmr', 'bms', 'bmt', 'bmu', 'bmv', 'bmw', 'bmx', 'bmz', 'bn', 'bna', 'bnb', 'bnc', 'bnd', 'bne', 'bnf', 'bng', 'bni', 'bnj', 'bnk', 'bnl', 'bnm', 'bnn', 'bno', 'bnp', 'bnq', 'bnr', 'bns', 'bnt', 'bnu', 'bnv', 'bnw', 'bnx', 'bny', 'bnz', 'bo', 'boa', 'bob', 'boe', 'bof', 'bog', 'boh', 'boi', 'boj', 'bok', 'bol', 'bom', 'bon', 'boo', 'bop', 'boq', 'bor', 'bot', 'bou', 'bov', 'bow', 'box', 'boy', 'boz', 'bpa', 'bpb', 'bpd', 'bpg', 'bph', 'bpi', 'bpj', 'bpk', 'bpl', 'bpm', 'bpn', 'bpo', 'bpp', 'bpq', 'bpr', 'bps', 'bpt', 'bpu', 'bpv', 'bpw', 'bpx', 'bpy', 'bpz', 'bqa', 'bqb', 'bqc', 'bqd', 'bqf', 'bqg', 'bqh', 'bqi', 'bqj', 'bqk', 'bql', 'bqm', 'bqn', 'bqo', 'bqp', 'bqq', 'bqr', 'bqs', 'bqt', 'bqu', 'bqv', 'bqw', 'bqx', 'bqy', 'bqz', 'br', 'bra', 'brb', 'brc', 'brd', 'brf', 'brg', 'brh', 'bri', 'brj', 'brk', 'brl', 'brm', 'brn', 'bro', 'brp', 'brq', 'brr', 'brs', 'brt', 'bru', 'brv', 'brw', 'brx', 'bry', 'brz', 'bs', 'bsa', 'bsb', 'bsc', 'bse', 'bsf', 'bsg', 'bsh', 'bsi', 'bsj', 'bsk', 'bsl', 'bsm', 'bsn', 'bso', 'bsp', 'bsq', 'bsr', 'bss', 'bst', 'bsu', 'bsv', 'bsw', 'bsx', 'bsy', 'bta', 'btc', 'btd', 'bte', 'btf', 'btg', 'bth', 'bti', 'btj', 'btk', 'btm', 'btn', 'bto', 'btp', 'btq', 'btr', 'bts', 'btt', 'btu', 'btv', 'btw', 'btx', 'bty', 'btz', 'bua', 'bub', 'buc', 'bud', 'bue', 'buf', 'bug', 'buh', 'bui', 'buj', 'buk', 'bum', 'bun', 'buo', 'bup', 'buq', 'bus', 'but', 'buu', 'buv', 'buw', 'bux', 'buy', 'buz', 'bva', 'bvb', 'bvc', 'bvd', 'bve', 'bvf', 'bvg', 'bvh', 'bvi', 'bvj', 'bvk', 'bvl', 'bvm', 'bvn', 'bvo', 'bvp', 'bvq', 'bvr', 'bvt', 'bvu', 'bvv', 'bvw', 'bvx', 'bvy', 'bvz', 'bwa', 'bwb', 'bwc', 'bwd', 'bwe', 'bwf', 'bwg', 'bwh', 'bwi', 'bwj', 'bwk', 'bwl', 'bwm', 'bwn', 'bwo', 'bwp', 'bwq', 'bwr', 'bws', 'bwt', 'bwu', 'bww', 'bwx', 'bwy', 'bwz', 'bxa', 'bxb', 'bxc', 'bxd', 'bxe', 'bxf', 'bxg', 'bxh', 'bxi', 'bxj', 'bxk', 'bxl', 'bxm', 'bxn', 'bxo', 'bxp', 'bxq', 'bxr', 'bxs', 'bxu', 'bxv', 'bxw', 'bxz', 'bya', 'byb', 'byc', 'byd', 'bye', 'byf', 'byg', 'byh', 'byi', 'byj', 'byk', 'byl', 'bym', 'byn', 'byo', 'byp', 'byq', 'byr', 'bys', 'byt', 'byv', 'byw', 'byx', 'byz', 'bza', 'bzb', 'bzc', 'bzd', 'bze', 'bzf', 'bzg', 'bzh', 'bzi', 'bzj', 'bzk', 'bzl', 'bzm', 'bzn', 'bzo', 'bzp', 'bzq', 'bzr', 'bzs', 'bzt', 'bzu', 'bzv', 'bzw', 'bzx', 'bzy', 'bzz', 'ca', 'caa', 'cab', 'cac', 'cad', 'cae', 'caf', 'cag', 'cah', 'cai', 'caj', 'cak', 'cal', 'cam', 'can', 'cao', 'cap', 'caq', 'car', 'cas', 'cau', 'cav', 'caw', 'cax', 'cay', 'caz', 'cbb', 'cbc', 'cbd', 'cbg', 'cbi', 'cbj', 'cbk', 'cbl', 'cbn', 'cbo', 'cbq', 'cbr', 'cbs', 'cbt', 'cbu', 'cbv', 'cbw', 'cby', 'cca', 'ccc', 'ccd', 'cce', 'ccg', 'cch', 'ccj', 'ccl', 'ccm', 'cco', 'ccp', 'ccr', 'cda', 'cde', 'cdf', 'cdg', 'cdh', 'cdi', 'cdj', 'cdm', 'cdn', 'cdo', 'cdr', 'cds', 'cdy', 'cdz', 'ce', 'cea', 'ceb', 'ceg', 'cek', 'cel', 'cen', 'cet', 'cfa', 'cfd', 'cfg', 'cfm', 'cga', 'cgc', 'cgg', 'cgk', 'ch', 'chb', 'chc', 'chd', 'chf', 'chg', 'chh', 'chj', 'chk', 'chl', 'chm', 'chn', 'cho', 'chp', 'chq', 'chr', 'cht', 'chw', 'chx', 'chy', 'chz', 'cia', 'cib', 'cic', 'cid', 'cie', 'cih', 'cik', 'cim', 'cin', 'cip', 'cir', 'ciw', 'ciy', 'cja', 'cje', 'cjh', 'cji', 'cjk', 'cjm', 'cjn', 'cjo', 'cjp', 'cjs', 'cjv', 'cjy', 'ckb', 'ckh', 'ckl', 'ckn', 'cko', 'ckq', 'ckr', 'cks', 'ckt', 'cku', 'ckv', 'ckx', 'cky', 'ckz', 'cla', 'clc', 'cld', 'cle', 'clh', 'cli', 'clj', 'clk', 'cll', 'clm', 'clo', 'clt', 'clu', 'clw', 'cly', 'cma', 'cmc', 'cme', 'cmg', 'cmi', 'cml', 'cmm', 'cmn', 'cmo', 'cmr', 'cms', 'cmt', 'cna', 'cnb', 'cnc', 'cng', 'cnh', 'cni', 'cnk', 'cnl', 'cno', 'cns', 'cnt', 'cnu', 'cnw', 'cnx', 'co', 'coa', 'cob', 'coc', 'cod', 'coe', 'cof', 'cog', 'coh', 'coj', 'cok', 'col', 'com', 'con', 'coo', 'cop', 'coq', 'cot', 'cou', 'cov', 'cow', 'cox', 'coz', 'cpa', 'cpb', 'cpc', 'cpe', 'cpf', 'cpg', 'cpi', 'cpn', 'cpo', 'cpp', 'cps', 'cpu', 'cpx', 'cpy', 'cqd', 'cr', 'cra', 'crb', 'crc', 'crd', 'crf', 'crg', 'crh', 'cri', 'crj', 'crk', 'crl', 'crm', 'crn', 'cro', 'crp', 'crq', 'crr', 'crs', 'crt', 'crv', 'crw', 'crx', 'cry', 'crz', 'cs', 'csa', 'csb', 'csc', 'csd', 'cse', 'csf', 'csg', 'csh', 'csi', 'csj', 'csk', 'csl', 'csm', 'csn', 'cso', 'csq', 'csr', 'css', 'cst', 'csv', 'csw', 'csy', 'csz', 'cta', 'ctc', 'ctd', 'cte', 'ctg', 'cth', 'ctl', 'ctm', 'ctn', 'cto', 'ctp', 'cts', 'ctt', 'ctu', 'ctz', 'cu', 'cua', 'cub', 'cuc', 'cug', 'cuh', 'cui', 'cuj', 'cuk', 'cul', 'cuo', 'cup', 'cuq', 'cur', 'cus', 'cut', 'cuu', 'cuv', 'cuw', 'cux', 'cv', 'cvg', 'cvn', 'cwa', 'cwb', 'cwd', 'cwe', 'cwg', 'cwt', 'cy', 'cya', 'cyb', 'cyo', 'czh', 'czk', 'czn', 'czo', 'czt', 'da', 'daa', 'dac', 'dad', 'dae', 'dag', 'dah', 'dai', 'daj', 'dak', 'dal', 'dam', 'dao', 'daq', 'dar', 'das', 'dau', 'dav', 'daw', 'dax', 'day', 'daz', 'dba', 'dbb', 'dbd', 'dbe', 'dbf', 'dbg', 'dbi', 'dbj', 'dbl', 'dbm', 'dbn', 'dbo', 'dbp', 'dbq', 'dbr', 'dbt', 'dbu', 'dbv', 'dbw', 'dby', 'dcc', 'dcr', 'dda', 'ddd', 'dde', 'ddg', 'ddi', 'ddj', 'ddn', 'ddo', 'ddr', 'dds', 'ddw', 'de', 'dec', 'ded', 'dee', 'def', 'deg', 'deh', 'dei', 'dek', 'del', 'dem', 'den', 'dep', 'deq', 'der', 'des', 'dev', 'dez', 'dga', 'dgb', 'dgc', 'dgd', 'dge', 'dgg', 'dgh', 'dgi', 'dgk', 'dgl', 'dgn', 'dgo', 'dgr', 'dgs', 'dgt', 'dgu', 'dgw', 'dgx', 'dgz', 'dhd', 'dhg', 'dhi', 'dhl', 'dhm', 'dhn', 'dho', 'dhr', 'dhs', 'dhu', 'dhv', 'dhw', 'dhx', 'dia', 'dib', 'dic', 'did', 'dif', 'dig', 'dih', 'dii', 'dij', 'dik', 'dil', 'dim', 'din', 'dio', 'dip', 'diq', 'dir', 'dis', 'dit', 'diu', 'diw', 'dix', 'diy', 'diz', 'dja', 'djb', 'djc', 'djd', 'dje', 'djf', 'dji', 'djj', 'djk', 'djm', 'djn', 'djo', 'djr', 'dju', 'djw', 'dka', 'dkk', 'dkr', 'dks', 'dkx', 'dlg', 'dlk', 'dlm', 'dln', 'dma', 'dmb', 'dmc', 'dmd', 'dme', 'dmg', 'dmk', 'dml', 'dmm', 'dmo', 'dmr', 'dms', 'dmu', 'dmv', 'dmw', 'dmx', 'dmy', 'dna', 'dnd', 'dne', 'dng', 'dni', 'dnj', 'dnk', 'dnn', 'dnr', 'dnt', 'dnu', 'dnv', 'dnw', 'dny', 'doa', 'dob', 'doc', 'doe', 'dof', 'doh', 'doi', 'dok', 'dol', 'don', 'doo', 'dop', 'doq', 'dor', 'dos', 'dot', 'dov', 'dow', 'dox', 'doy', 'doz', 'dpp', 'dra', 'drb', 'drc', 'drd', 'dre', 'drg', 'dri', 'drl', 'drn', 'dro', 'drq', 'drr', 'drs', 'drt', 'dru', 'dry', 'dsb', 'dse', 'dsh', 'dsi', 'dsl', 'dsn', 'dso', 'dsq', 'dta', 'dtb', 'dtd', 'dth', 'dti', 'dtk', 'dtm', 'dtn', 'dto', 'dtp', 'dtr', 'dts', 'dtt', 'dtu', 'dty', 'dua', 'dub', 'duc', 'dud', 'due', 'duf', 'dug', 'duh', 'dui', 'duk', 'dul', 'dum', 'dun', 'duo', 'dup', 'duq', 'dur', 'dus', 'duu', 'duv', 'duw', 'dux', 'duy', 'duz', 'dv', 'dva', 'dwa', 'dwr', 'dws', 'dwu', 'dww', 'dwy', 'dya', 'dyb', 'dyd', 'dyg', 'dyi', 'dym', 'dyn', 'dyo', 'dyu', 'dyy', 'dz', 'dza', 'dze', 'dzg', 'dzl', 'dzn', 'eaa', 'ebg', 'ebk', 'ebo', 'ebr', 'ebu', 'ecr', 'ecs', 'ecy', 'ee', 'eee', 'efa', 'efe', 'efi', 'ega', 'egl', 'ego', 'egy', 'ehu', 'eip', 'eit', 'eiv', 'eja', 'eka', 'ekc', 'eke', 'ekg', 'eki', 'ekk', 'ekl', 'ekm', 'eko', 'ekp', 'ekr', 'eky', 'el', 'ele', 'elh', 'eli', 'elk', 'elm', 'elo', 'elu', 'elx', 'ema', 'emb', 'eme', 'emg', 'emi', 'emk', 'emm', 'emn', 'emp', 'ems', 'emu', 'emw', 'emx', 'emy', 'en', 'ena', 'enb', 'enc', 'end', 'enf', 'enh', 'enl', 'enm', 'enn', 'eno', 'enq', 'enr', 'enu', 'env', 'enw', 'enx', 'eo', 'eot', 'epi', 'era', 'erg', 'erh', 'eri', 'erk', 'ero', 'err', 'ers', 'ert', 'erw', 'es', 'ese', 'esg', 'esh', 'esi', 'esk', 'esl', 'esm', 'esn', 'eso', 'esq', 'ess', 'esu', 'esy', 'et', 'etb', 'etc', 'eth', 'etn', 'eto', 'etr', 'ets', 'ett', 'etu', 'etx', 'etz', 'eu', 'eve', 'evh', 'evn', 'ewo', 'ext', 'eya', 'eyo', 'eza', 'eze', 'fa', 'faa', 'fab', 'fad', 'faf', 'fag', 'fah', 'fai', 'faj', 'fak', 'fal', 'fam', 'fan', 'fap', 'far', 'fat', 'fau', 'fax', 'fay', 'faz', 'fbl', 'fcs', 'fer', 'ff', 'ffi', 'ffm', 'fgr', 'fi', 'fia', 'fie', 'fil', 'fip', 'fir', 'fit', 'fiu', 'fiw', 'fj', 'fkk', 'fkv', 'fla', 'flh', 'fli', 'fll', 'fln', 'flr', 'fly', 'fmp', 'fmu', 'fnb', 'fng', 'fni', 'fo', 'fod', 'foi', 'fom', 'fon', 'for', 'fos', 'fpe', 'fqs', 'fr', 'frc', 'frd', 'frk', 'frm', 'fro', 'frp', 'frq', 'frr', 'frs', 'frt', 'fse', 'fsl', 'fss', 'fub', 'fuc', 'fud', 'fue', 'fuf', 'fuh', 'fui', 'fuj', 'fum', 'fun', 'fuq', 'fur', 'fut', 'fuu', 'fuv', 'fuy', 'fvr', 'fwa', 'fwe', 'fy', 'ga', 'gaa', 'gab', 'gac', 'gad', 'gae', 'gaf', 'gag', 'gah', 'gai', 'gaj', 'gak', 'gal', 'gam', 'gan', 'gao', 'gap', 'gaq', 'gar', 'gas', 'gat', 'gau', 'gaw', 'gax', 'gay', 'gaz', 'gba', 'gbb', 'gbd', 'gbe', 'gbf', 'gbg', 'gbh', 'gbi', 'gbj', 'gbk', 'gbl', 'gbm', 'gbn', 'gbo', 'gbp', 'gbq', 'gbr', 'gbs', 'gbu', 'gbv', 'gbw', 'gbx', 'gby', 'gbz', 'gcc', 'gcd', 'gce', 'gcf', 'gcl', 'gcn', 'gcr', 'gct', 'gd', 'gda', 'gdb', 'gdc', 'gdd', 'gde', 'gdf', 'gdg', 'gdh', 'gdi', 'gdj', 'gdk', 'gdl', 'gdm', 'gdn', 'gdo', 'gdq', 'gdr', 'gds', 'gdt', 'gdu', 'gdx', 'gea', 'geb', 'gec', 'ged', 'geg', 'geh', 'gei', 'gej', 'gek', 'gel', 'gem', 'geq', 'ges', 'gev', 'gew', 'gex', 'gey', 'gez', 'gfk', 'gft', 'gga', 'ggb', 'ggd', 'gge', 'ggg', 'ggk', 'ggl', 'ggt', 'ggu', 'ggw', 'gha', 'ghc', 'ghe', 'ghh', 'ghk', 'ghl', 'ghn', 'gho', 'ghr', 'ghs', 'ght', 'gia', 'gib', 'gic', 'gid', 'gig', 'gih', 'gil', 'gim', 'gin', 'gip', 'giq', 'gir', 'gis', 'git', 'giu', 'giw', 'gix', 'giy', 'giz', 'gji', 'gjk', 'gjm', 'gjn', 'gjr', 'gju', 'gka', 'gke', 'gkn', 'gko', 'gkp', 'gku', 'gl', 'glc', 'gld', 'glh', 'gli', 'glj', 'glk', 'gll', 'glo', 'glr', 'glu', 'glw', 'gly', 'gma', 'gmb', 'gmd', 'gmg', 'gmh', 'gml', 'gmm', 'gmn', 'gmu', 'gmv', 'gmx', 'gmy', 'gmz', 'gn', 'gna', 'gnb', 'gnc', 'gnd', 'gne', 'gng', 'gnh', 'gni', 'gnk', 'gnl', 'gnm', 'gnn', 'gno', 'gnq', 'gnr', 'gnt', 'gnu', 'gnw', 'gnz', 'goa', 'gob', 'goc', 'god', 'goe', 'gof', 'gog', 'goh', 'goi', 'goj', 'gok', 'gol', 'gom', 'gon', 'goo', 'gop', 'goq', 'gor', 'gos', 'got', 'gou', 'gow', 'gox', 'goy', 'goz', 'gpa', 'gpe', 'gpn', 'gqa', 'gqi', 'gqn', 'gqr', 'gqu', 'gra', 'grb', 'grc', 'grd', 'grg', 'grh', 'gri', 'grj', 'grm', 'gro', 'grq', 'grr', 'grs', 'grt', 'gru', 'grv', 'grw', 'grx', 'gry', 'grz', 'gse', 'gsg', 'gsl', 'gsm', 'gsn', 'gso', 'gsp', 'gss', 'gsw', 'gta', 'gtu', 'gu', 'gua', 'gub', 'guc', 'gud', 'gue', 'guf', 'gug', 'guh', 'gui', 'guk', 'gul', 'gum', 'gun', 'guo', 'gup', 'guq', 'gur', 'gus', 'gut', 'guu', 'guw', 'gux', 'guz', 'gv', 'gva', 'gvc', 'gve', 'gvf', 'gvj', 'gvl', 'gvm', 'gvn', 'gvo', 'gvp', 'gvr', 'gvs', 'gvy', 'gwa', 'gwb', 'gwc', 'gwd', 'gwe', 'gwf', 'gwg', 'gwi', 'gwj', 'gwm', 'gwn', 'gwr', 'gwt', 'gwu', 'gww', 'gwx', 'gxx', 'gya', 'gyb', 'gyd', 'gye', 'gyf', 'gyg', 'gyi', 'gyl', 'gym', 'gyn', 'gyr', 'gyy', 'gza', 'gzi', 'gzn', 'ha', 'haa', 'hab', 'hac', 'had', 'hae', 'haf', 'hag', 'hah', 'hai', 'haj', 'hak', 'hal', 'ham', 'han', 'hao', 'hap', 'haq', 'har', 'has', 'hav', 'haw', 'hax', 'hay', 'haz', 'hba', 'hbb', 'hbn', 'hbo', 'hbu', 'hca', 'hch', 'hdn', 'hds', 'hdy', 'he', 'hea', 'hed', 'heg', 'heh', 'hei', 'hem', 'hgm', 'hgw', 'hhi', 'hhr', 'hhy', 'hi', 'hia', 'hib', 'hid', 'hif', 'hig', 'hih', 'hii', 'hij', 'hik', 'hil', 'him', 'hio', 'hir', 'hit', 'hiw', 'hix', 'hji', 'hka', 'hke', 'hkk', 'hks', 'hla', 'hlb', 'hld', 'hle', 'hlt', 'hlu', 'hma', 'hmb', 'hmc', 'hmd', 'hme', 'hmf', 'hmg', 'hmh', 'hmi', 'hmj', 'hmk', 'hml', 'hmm', 'hmn', 'hmp', 'hmq', 'hmr', 'hms', 'hmt', 'hmu', 'hmv', 'hmw', 'hmy', 'hmz', 'hna', 'hnd', 'hne', 'hnh', 'hni', 'hnj', 'hnn', 'hno', 'hns', 'hnu', 'ho', 'hoa', 'hob', 'hoc', 'hod', 'hoe', 'hoh', 'hoi', 'hoj', 'hol', 'hom', 'hoo', 'hop', 'hor', 'hos', 'hot', 'hov', 'how', 'hoy', 'hoz', 'hpo', 'hps', 'hr', 'hra', 'hrc', 'hre', 'hrk', 'hrm', 'hro', 'hrp', 'hrt', 'hru', 'hrw', 'hrx', 'hrz', 'hsb', 'hsh', 'hsl', 'hsn', 'hss', 'ht', 'hti', 'hto', 'hts', 'htu', 'htx', 'hu', 'hub', 'huc', 'hud', 'hue', 'huf', 'hug', 'huh', 'hui', 'huj', 'huk', 'hul', 'hum', 'huo', 'hup', 'huq', 'hur', 'hus', 'hut', 'huu', 'huv', 'huw', 'hux', 'huy', 'huz', 'hvc', 'hve', 'hvk', 'hvn', 'hvv', 'hwa', 'hwc', 'hwo', 'hy', 'hya', 'hz', 'ia', 'iai', 'ian', 'iar', 'iba', 'ibb', 'ibd', 'ibe', 'ibg', 'ibl', 'ibm', 'ibn', 'ibr', 'ibu', 'iby', 'ica', 'ich', 'icl', 'icr', 'id', 'ida', 'idb', 'idc', 'idd', 'ide', 'idi', 'idr', 'ids', 'idt', 'idu', 'ie', 'ifa', 'ifb', 'ife', 'iff', 'ifk', 'ifm', 'ifu', 'ify', 'ig', 'igb', 'ige', 'igg', 'igl', 'igm', 'ign', 'igo', 'igs', 'igw', 'ihb', 'ihi', 'ihp', 'ihw', 'ii', 'iin', 'ijc', 'ije', 'ijj', 'ijn', 'ijo', 'ijs', 'ik', 'ike', 'iki', 'ikk', 'ikl', 'iko', 'ikp', 'ikr', 'iks', 'ikt', 'ikv', 'ikw', 'ikx', 'ikz', 'ila', 'ilb', 'ilg', 'ili', 'ilk', 'ilm', 'ilo', 'ilp', 'ils', 'ilu', 'ilv', 'ima', 'imi', 'iml', 'imn', 'imo', 'imr', 'ims', 'imy', 'inb', 'inc', 'ine', 'ing', 'inh', 'inj', 'inl', 'inm', 'inn', 'ino', 'inp', 'ins', 'int', 'inz', 'io', 'ior', 'iou', 'iow', 'ipi', 'ipo', 'iqu', 'iqw', 'ira', 'ire', 'irh', 'iri', 'irk', 'irn', 'iro', 'irr', 'iru', 'irx', 'iry', 'is', 'isa', 'isc', 'isd', 'ise', 'isg', 'ish', 'isi', 'isk', 'ism', 'isn', 'iso', 'isr', 'ist', 'isu', 'it', 'itb', 'itd', 'ite', 'iti', 'itk', 'itl', 'itm', 'ito', 'itr', 'its', 'itt', 'itv', 'itw', 'itx', 'ity', 'itz', 'iu', 'ium', 'ivb', 'ivv', 'iwk', 'iwm', 'iwo', 'iws', 'ixc', 'ixl', 'iya', 'iyo', 'iyx', 'izh', 'izr', 'izz', 'ja', 'jaa', 'jab', 'jac', 'jad', 'jae', 'jaf', 'jah', 'jaj', 'jak', 'jal', 'jam', 'jan', 'jao', 'jaq', 'jas', 'jat', 'jau', 'jax', 'jay', 'jaz', 'jbe', 'jbi', 'jbj', 'jbk', 'jbn', 'jbo', 'jbr', 'jbt', 'jbu', 'jbw', 'jcs', 'jct', 'jda', 'jdg', 'jdt', 'jeb', 'jee', 'jeg', 'jeh', 'jei', 'jek', 'jel', 'jen', 'jer', 'jet', 'jeu', 'jgb', 'jge', 'jgk', 'jgo', 'jhi', 'jhs', 'jia', 'jib', 'jic', 'jid', 'jie', 'jig', 'jih', 'jii', 'jil', 'jim', 'jio', 'jiq', 'jit', 'jiu', 'jiv', 'jiy', 'jje', 'jjr', 'jka', 'jkm', 'jko', 'jkp', 'jkr', 'jku', 'jle', 'jls', 'jma', 'jmb', 'jmc', 'jmd', 'jmi', 'jml', 'jmn', 'jmr', 'jms', 'jmw', 'jmx', 'jna', 'jnd', 'jng', 'jni', 'jnj', 'jnl', 'jns', 'job', 'jod', 'jog', 'jor', 'jos', 'jow', 'jpa', 'jpr', 'jqr', 'jra', 'jrb', 'jrr', 'jrt', 'jru', 'jsl', 'jua', 'jub', 'juc', 'jud', 'juh', 'jui', 'juk', 'jul', 'jum', 'jun', 'juo', 'jup', 'jur', 'jus', 'jut', 'juu', 'juw', 'juy', 'jv', 'jvd', 'jvn', 'jwi', 'jya', 'jye', 'jyy', 'ka', 'kaa', 'kab', 'kac', 'kad', 'kae', 'kaf', 'kag', 'kah', 'kai', 'kaj', 'kak', 'kam', 'kao', 'kap', 'kaq', 'kar', 'kav', 'kaw', 'kax', 'kay', 'kba', 'kbb', 'kbc', 'kbd', 'kbe', 'kbg', 'kbh', 'kbi', 'kbj', 'kbk', 'kbl', 'kbm', 'kbn', 'kbo', 'kbp', 'kbq', 'kbr', 'kbs', 'kbt', 'kbu', 'kbv', 'kbw', 'kbx', 'kby', 'kbz', 'kca', 'kcb', 'kcc', 'kcd', 'kce', 'kcf', 'kcg', 'kch', 'kci', 'kcj', 'kck', 'kcl', 'kcm', 'kcn', 'kco', 'kcp', 'kcq', 'kcr', 'kcs', 'kct', 'kcu', 'kcv', 'kcw', 'kcx', 'kcy', 'kcz', 'kda', 'kdc', 'kdd', 'kde', 'kdf', 'kdg', 'kdh', 'kdi', 'kdj', 'kdk', 'kdl', 'kdm', 'kdn', 'kdp', 'kdq', 'kdr', 'kdt', 'kdu', 'kdw', 'kdx', 'kdy', 'kdz', 'kea', 'keb', 'kec', 'ked', 'kee', 'kef', 'keg', 'keh', 'kei', 'kej', 'kek', 'kel', 'kem', 'ken', 'keo', 'kep', 'keq', 'ker', 'kes', 'ket', 'keu', 'kev', 'kew', 'kex', 'key', 'kez', 'kfa', 'kfb', 'kfc', 'kfd', 'kfe', 'kff', 'kfg', 'kfh', 'kfi', 'kfj', 'kfk', 'kfl', 'kfm', 'kfn', 'kfo', 'kfp', 'kfq', 'kfr', 'kfs', 'kft', 'kfu', 'kfv', 'kfw', 'kfx', 'kfy', 'kfz', 'kg', 'kga', 'kgb', 'kgd', 'kge', 'kgf', 'kgg', 'kgi', 'kgj', 'kgk', 'kgl', 'kgm', 'kgn', 'kgo', 'kgp', 'kgq', 'kgr', 'kgs', 'kgt', 'kgu', 'kgv', 'kgw', 'kgx', 'kgy', 'kha', 'khb', 'khc', 'khd', 'khe', 'khf', 'khg', 'khh', 'khi', 'khj', 'khk', 'khl', 'khn', 'kho', 'khp', 'khq', 'khr', 'khs', 'kht', 'khu', 'khv', 'khw', 'khx', 'khy', 'khz', 'ki', 'kia', 'kib', 'kic', 'kid', 'kie', 'kif', 'kig', 'kih', 'kii', 'kij', 'kil', 'kim', 'kio', 'kip', 'kiq', 'kis', 'kit', 'kiu', 'kiv', 'kiw', 'kix', 'kiy', 'kiz', 'kj', 'kja', 'kjb', 'kjc', 'kjd', 'kje', 'kjf', 'kjg', 'kjh', 'kji', 'kjj', 'kjk', 'kjl', 'kjm', 'kjn', 'kjo', 'kjp', 'kjq', 'kjr', 'kjs', 'kjt', 'kju', 'kjv', 'kjx', 'kjy', 'kjz', 'kk', 'kka', 'kkb', 'kkc', 'kkd', 'kke', 'kkf', 'kkg', 'kkh', 'kki', 'kkj', 'kkk', 'kkl', 'kkm', 'kkn', 'kko', 'kkp', 'kkq', 'kkr', 'kks', 'kkt', 'kku', 'kkv', 'kkw', 'kkx', 'kky', 'kkz', 'kl', 'kla', 'klb', 'klc', 'kld', 'kle', 'klf', 'klg', 'klh', 'kli', 'klj', 'klk', 'kll', 'klm', 'kln', 'klo', 'klp', 'klq', 'klr', 'kls', 'klt', 'klu', 'klv', 'klw', 'klx', 'kly', 'klz', 'km', 'kma', 'kmb', 'kmc', 'kmd', 'kme', 'kmf', 'kmg', 'kmh', 'kmi', 'kmj', 'kmk', 'kml', 'kmm', 'kmn', 'kmo', 'kmp', 'kmq', 'kmr', 'kms', 'kmt', 'kmu', 'kmv', 'kmw', 'kmx', 'kmy', 'kmz', 'kn', 'kna', 'knb', 'knc', 'knd', 'kne', 'knf', 'kng', 'kni', 'knj', 'knk', 'knl', 'knm', 'knn', 'kno', 'knp', 'knq', 'knr', 'kns', 'knt', 'knu', 'knv', 'knw', 'knx', 'kny', 'knz', 'ko', 'koa', 'koc', 'kod', 'koe', 'kof', 'kog', 'koh', 'koi', 'kok', 'kol', 'koo', 'kop', 'koq', 'kos', 'kot', 'kou', 'kov', 'kow', 'koy', 'koz', 'kpa', 'kpb', 'kpc', 'kpd', 'kpe', 'kpf', 'kpg', 'kph', 'kpi', 'kpj', 'kpk', 'kpl', 'kpm', 'kpn', 'kpo', 'kpq', 'kpr', 'kps', 'kpt', 'kpu', 'kpv', 'kpw', 'kpx', 'kpy', 'kpz', 'kqa', 'kqb', 'kqc', 'kqd', 'kqe', 'kqf', 'kqg', 'kqh', 'kqi', 'kqj', 'kqk', 'kql', 'kqm', 'kqn', 'kqo', 'kqp', 'kqq', 'kqr', 'kqs', 'kqt', 'kqu', 'kqv', 'kqw', 'kqx', 'kqy', 'kqz', 'kr', 'kra', 'krb', 'krc', 'krd', 'kre', 'krf', 'krh', 'kri', 'krj', 'krk', 'krl', 'krm', 'krn', 'kro', 'krp', 'krr', 'krs', 'krt', 'kru', 'krv', 'krw', 'krx', 'kry', 'krz', 'ks', 'ksa', 'ksb', 'ksc', 'ksd', 'kse', 'ksf', 'ksg', 'ksh', 'ksi', 'ksj', 'ksk', 'ksl', 'ksm', 'ksn', 'kso', 'ksp', 'ksq', 'ksr', 'kss', 'kst', 'ksu', 'ksv', 'ksw', 'ksx', 'ksy', 'ksz', 'kta', 'ktb', 'ktc', 'ktd', 'kte', 'ktf', 'ktg', 'kth', 'kti', 'ktj', 'ktk', 'ktl', 'ktm', 'ktn', 'kto', 'ktp', 'ktq', 'kts', 'ktt', 'ktu', 'ktv', 'ktw', 'ktx', 'kty', 'ktz', 'ku', 'kub', 'kuc', 'kud', 'kue', 'kuf', 'kug', 'kuh', 'kui', 'kuj', 'kuk', 'kul', 'kum', 'kun', 'kuo', 'kup', 'kuq', 'kus', 'kut', 'kuu', 'kuv', 'kuw', 'kux', 'kuy', 'kuz', 'kv', 'kva', 'kvb', 'kvc', 'kvd', 'kve', 'kvf', 'kvg', 'kvh', 'kvi', 'kvj', 'kvk', 'kvl', 'kvm', 'kvn', 'kvo', 'kvp', 'kvq', 'kvr', 'kvt', 'kvu', 'kvv', 'kvw', 'kvx', 'kvy', 'kvz', 'kw', 'kwa', 'kwb', 'kwc', 'kwd', 'kwe', 'kwf', 'kwg', 'kwh', 'kwi', 'kwj', 'kwk', 'kwl', 'kwm', 'kwn', 'kwo', 'kwp', 'kwr', 'kws', 'kwt', 'kwu', 'kwv', 'kww', 'kwx', 'kwy', 'kwz', 'kxa', 'kxb', 'kxc', 'kxd', 'kxf', 'kxh', 'kxi', 'kxj', 'kxk', 'kxl', 'kxm', 'kxn', 'kxo', 'kxp', 'kxq', 'kxr', 'kxs', 'kxt', 'kxu', 'kxv', 'kxw', 'kxx', 'kxy', 'kxz', 'ky', 'kya', 'kyb', 'kyc', 'kyd', 'kye', 'kyf', 'kyg', 'kyh', 'kyi', 'kyj', 'kyk', 'kyl', 'kym', 'kyn', 'kyo', 'kyp', 'kyq', 'kyr', 'kys', 'kyt', 'kyu', 'kyv', 'kyw', 'kyx', 'kyy', 'kyz', 'kza', 'kzb', 'kzc', 'kzd', 'kze', 'kzf', 'kzg', 'kzi', 'kzk', 'kzl', 'kzm', 'kzn', 'kzo', 'kzp', 'kzq', 'kzr', 'kzs', 'kzu', 'kzv', 'kzw', 'kzx', 'kzy', 'kzz', 'la', 'laa', 'lab', 'lac', 'lad', 'lae', 'laf', 'lag', 'lah', 'lai', 'laj', 'lak', 'lal', 'lam', 'lan', 'lap', 'laq', 'lar', 'las', 'lau', 'law', 'lax', 'lay', 'laz', 'lb', 'lba', 'lbb', 'lbc', 'lbe', 'lbf', 'lbg', 'lbi', 'lbj', 'lbk', 'lbl', 'lbm', 'lbn', 'lbo', 'lbq', 'lbr', 'lbs', 'lbt', 'lbu', 'lbv', 'lbw', 'lbx', 'lby', 'lbz', 'lcc', 'lcd', 'lce', 'lcf', 'lch', 'lcl', 'lcm', 'lcp', 'lcq', 'lcs', 'lda', 'ldb', 'ldd', 'ldg', 'ldh', 'ldi', 'ldj', 'ldk', 'ldl', 'ldm', 'ldn', 'ldo', 'ldp', 'ldq', 'lea', 'leb', 'lec', 'led', 'lee', 'lef', 'leh', 'lei', 'lej', 'lek', 'lel', 'lem', 'len', 'leo', 'lep', 'leq', 'ler', 'les', 'let', 'leu', 'lev', 'lew', 'lex', 'ley', 'lez', 'lfa', 'lfn', 'lg', 'lga', 'lgb', 'lgg', 'lgh', 'lgi', 'lgk', 'lgl', 'lgm', 'lgn', 'lgq', 'lgr', 'lgt', 'lgu', 'lgz', 'lha', 'lhh', 'lhi', 'lhl', 'lhm', 'lhn', 'lhp', 'lhs', 'lht', 'lhu', 'li', 'lia', 'lib', 'lic', 'lid', 'lie', 'lif', 'lig', 'lih', 'lij', 'lik', 'lil', 'lio', 'lip', 'liq', 'lir', 'lis', 'liu', 'liv', 'liw', 'lix', 'liy', 'liz', 'lja', 'lje', 'lji', 'ljl', 'ljp', 'ljw', 'ljx', 'lka', 'lkb', 'lkc', 'lkd', 'lke', 'lkh', 'lki', 'lkj', 'lkl', 'lkm', 'lkn', 'lko', 'lkr', 'lks', 'lkt', 'lku', 'lky', 'lla', 'llb', 'llc', 'lld', 'lle', 'llf', 'llg', 'llh', 'lli', 'llj', 'llk', 'lll', 'llm', 'lln', 'llo', 'llp', 'llq', 'lls', 'llu', 'llx', 'lma', 'lmb', 'lmc', 'lmd', 'lme', 'lmf', 'lmg', 'lmh', 'lmi', 'lmj', 'lmk', 'lml', 'lmn', 'lmo', 'lmp', 'lmq', 'lmr', 'lmu', 'lmv', 'lmw', 'lmx', 'lmy', 'lmz', 'ln', 'lna', 'lnb', 'lnd', 'lng', 'lnh', 'lni', 'lnj', 'lnl', 'lnm', 'lnn', 'lno', 'lns', 'lnu', 'lnw', 'lnz', 'lo', 'loa', 'lob', 'loc', 'loe', 'lof', 'log', 'loh', 'loi', 'loj', 'lok', 'lol', 'lom', 'lon', 'loo', 'lop', 'loq', 'lor', 'los', 'lot', 'lou', 'lov', 'low', 'lox', 'loy', 'loz', 'lpa', 'lpe', 'lpn', 'lpo', 'lpx', 'lra', 'lrc', 'lre', 'lrg', 'lri', 'lrk', 'lrl', 'lrm', 'lrn', 'lro', 'lrr', 'lrt', 'lrv', 'lrz', 'lsa', 'lsd', 'lse', 'lsg', 'lsh', 'lsi', 'lsl', 'lsm', 'lso', 'lsp', 'lsr', 'lss', 'lst', 'lsy', 'lt', 'ltc', 'ltg', 'lti', 'ltn', 'lto', 'lts', 'ltu', 'lu', 'lua', 'luc', 'lud', 'lue', 'luf', 'lui', 'luj', 'luk', 'lul', 'lum', 'lun', 'luo', 'lup', 'luq', 'lur', 'lus', 'lut', 'luu', 'luv', 'luw', 'luy', 'luz', 'lv', 'lva', 'lvk', 'lvs', 'lvu', 'lwa', 'lwe', 'lwg', 'lwh', 'lwl', 'lwm', 'lwo', 'lwt', 'lwu', 'lww', 'lya', 'lyg', 'lyn', 'lzh', 'lzl', 'lzn', 'lzz', 'maa', 'mab', 'mad', 'mae', 'maf', 'mag', 'mai', 'maj', 'mak', 'mam', 'man', 'map', 'maq', 'mas', 'mat', 'mau', 'mav', 'maw', 'max', 'maz', 'mba', 'mbb', 'mbc', 'mbd', 'mbe', 'mbf', 'mbh', 'mbi', 'mbj', 'mbk', 'mbl', 'mbm', 'mbn', 'mbo', 'mbp', 'mbq', 'mbr', 'mbs', 'mbt', 'mbu', 'mbv', 'mbw', 'mbx', 'mby', 'mbz', 'mca', 'mcb', 'mcc', 'mcd', 'mce', 'mcf', 'mcg', 'mch', 'mci', 'mcj', 'mck', 'mcl', 'mcm', 'mcn', 'mco', 'mcp', 'mcq', 'mcr', 'mcs', 'mct', 'mcu', 'mcv', 'mcw', 'mcx', 'mcy', 'mcz', 'mda', 'mdb', 'mdc', 'mdd', 'mde', 'mdf', 'mdg', 'mdh', 'mdi', 'mdj', 'mdk', 'mdl', 'mdm', 'mdn', 'mdp', 'mdq', 'mdr', 'mds', 'mdt', 'mdu', 'mdv', 'mdw', 'mdx', 'mdy', 'mdz', 'mea', 'meb', 'mec', 'med', 'mee', 'mef', 'meh', 'mei', 'mej', 'mek', 'mel', 'mem', 'men', 'meo', 'mep', 'meq', 'mer', 'mes', 'met', 'meu', 'mev', 'mew', 'mey', 'mez', 'mfa', 'mfb', 'mfc', 'mfd', 'mfe', 'mff', 'mfg', 'mfh', 'mfi', 'mfj', 'mfk', 'mfl', 'mfm', 'mfn', 'mfo', 'mfp', 'mfq', 'mfr', 'mfs', 'mft', 'mfu', 'mfv', 'mfw', 'mfx', 'mfy', 'mfz', 'mg', 'mga', 'mgb', 'mgc', 'mgd', 'mge', 'mgf', 'mgg', 'mgh', 'mgi', 'mgj', 'mgk', 'mgl', 'mgm', 'mgn', 'mgo', 'mgp', 'mgq', 'mgr', 'mgs', 'mgt', 'mgu', 'mgv', 'mgw', 'mgy', 'mgz', 'mh', 'mha', 'mhb', 'mhc', 'mhd', 'mhe', 'mhf', 'mhg', 'mhi', 'mhj', 'mhk', 'mhl', 'mhm', 'mhn', 'mho', 'mhp', 'mhq', 'mhr', 'mhs', 'mht', 'mhu', 'mhw', 'mhx', 'mhy', 'mhz', 'mi', 'mia', 'mib', 'mic', 'mid', 'mie', 'mif', 'mig', 'mih', 'mii', 'mij', 'mik', 'mil', 'mim', 'min', 'mio', 'mip', 'miq', 'mir', 'mis', 'mit', 'miu', 'miw', 'mix', 'miy', 'miz', 'mjb', 'mjc', 'mjd', 'mje', 'mjg', 'mjh', 'mji', 'mjj', 'mjk', 'mjl', 'mjm', 'mjn', 'mjo', 'mjp', 'mjq', 'mjr', 'mjs', 'mjt', 'mju', 'mjv', 'mjw', 'mjx', 'mjy', 'mjz', 'mk', 'mka', 'mkb', 'mkc', 'mke', 'mkf', 'mkg', 'mkh', 'mki', 'mkj', 'mkk', 'mkl', 'mkm', 'mkn', 'mko', 'mkp', 'mkq', 'mkr', 'mks', 'mkt', 'mku', 'mkv', 'mkw', 'mkx', 'mky', 'mkz', 'ml', 'mla', 'mlb', 'mlc', 'mle', 'mlf', 'mlh', 'mli', 'mlj', 'mlk', 'mll', 'mlm', 'mln', 'mlo', 'mlp', 'mlq', 'mlr', 'mls', 'mlu', 'mlv', 'mlw', 'mlx', 'mlz', 'mma', 'mmb', 'mmc', 'mmd', 'mme', 'mmf', 'mmg', 'mmh', 'mmi', 'mmj', 'mmk', 'mml', 'mmm', 'mmn', 'mmo', 'mmp', 'mmq', 'mmr', 'mmt', 'mmu', 'mmv', 'mmw', 'mmx', 'mmy', 'mmz', 'mn', 'mna', 'mnb', 'mnc', 'mnd', 'mne', 'mnf', 'mng', 'mnh', 'mni', 'mnj', 'mnk', 'mnl', 'mnm', 'mnn', 'mno', 'mnp', 'mnq', 'mnr', 'mns', 'mnu', 'mnv', 'mnw', 'mnx', 'mny', 'mnz', 'moa', 'moc', 'mod', 'moe', 'mog', 'moh', 'moi', 'moj', 'mok', 'mom', 'moo', 'mop', 'moq', 'mor', 'mos', 'mot', 'mou', 'mov', 'mow', 'mox', 'moy', 'moz', 'mpa', 'mpb', 'mpc', 'mpd', 'mpe', 'mpg', 'mph', 'mpi', 'mpj', 'mpk', 'mpl', 'mpm', 'mpn', 'mpo', 'mpp', 'mpq', 'mpr', 'mps', 'mpt', 'mpu', 'mpv', 'mpw', 'mpx', 'mpy', 'mpz', 'mqa', 'mqb', 'mqc', 'mqe', 'mqf', 'mqg', 'mqh', 'mqi', 'mqj', 'mqk', 'mql', 'mqm', 'mqn', 'mqo', 'mqp', 'mqq', 'mqr', 'mqs', 'mqt', 'mqu', 'mqv', 'mqw', 'mqx', 'mqy', 'mqz', 'mr', 'mra', 'mrb', 'mrc', 'mrd', 'mre', 'mrf', 'mrg', 'mrh', 'mrj', 'mrk', 'mrl', 'mrm', 'mrn', 'mro', 'mrp', 'mrq', 'mrr', 'mrs', 'mrt', 'mru', 'mrv', 'mrw', 'mrx', 'mry', 'mrz', 'ms', 'msb', 'msc', 'msd', 'mse', 'msf', 'msg', 'msh', 'msi', 'msj', 'msk', 'msl', 'msm', 'msn', 'mso', 'msp', 'msq', 'msr', 'mss', 'msu', 'msv', 'msw', 'msx', 'msy', 'msz', 'mt', 'mta', 'mtb', 'mtc', 'mtd', 'mte', 'mtf', 'mtg', 'mth', 'mti', 'mtj', 'mtk', 'mtl', 'mtm', 'mtn', 'mto', 'mtp', 'mtq', 'mtr', 'mts', 'mtt', 'mtu', 'mtv', 'mtw', 'mtx', 'mty', 'mua', 'mub', 'muc', 'mud', 'mue', 'mug', 'muh', 'mui', 'muj', 'muk', 'mul', 'mum', 'mun', 'muo', 'mup', 'muq', 'mur', 'mus', 'mut', 'muu', 'muv', 'mux', 'muy', 'muz', 'mva', 'mvb', 'mvd', 'mve', 'mvf', 'mvg', 'mvh', 'mvi', 'mvk', 'mvl', 'mvm', 'mvn', 'mvo', 'mvp', 'mvq', 'mvr', 'mvs', 'mvt', 'mvu', 'mvv', 'mvw', 'mvx', 'mvy', 'mvz', 'mwa', 'mwb', 'mwc', 'mwe', 'mwf', 'mwg', 'mwh', 'mwi', 'mwk', 'mwl', 'mwm', 'mwn', 'mwo', 'mwp', 'mwq', 'mwr', 'mws', 'mwt', 'mwu', 'mwv', 'mww', 'mwx', 'mwy', 'mwz', 'mxa', 'mxb', 'mxc', 'mxd', 'mxe', 'mxf', 'mxg', 'mxh', 'mxi', 'mxj', 'mxk', 'mxl', 'mxm', 'mxn', 'mxo', 'mxp', 'mxq', 'mxr', 'mxs', 'mxt', 'mxu', 'mxv', 'mxw', 'mxx', 'mxy', 'mxz', 'my', 'myb', 'myc', 'myd', 'mye', 'myf', 'myg', 'myh', 'myi', 'myj', 'myk', 'myl', 'mym', 'myn', 'myo', 'myp', 'myr', 'mys', 'myu', 'myv', 'myw', 'myx', 'myy', 'myz', 'mza', 'mzb', 'mzc', 'mzd', 'mze', 'mzg', 'mzh', 'mzi', 'mzj', 'mzk', 'mzl', 'mzm', 'mzn', 'mzo', 'mzp', 'mzq', 'mzr', 'mzs', 'mzt', 'mzu', 'mzv', 'mzw', 'mzx', 'mzy', 'mzz', 'na', 'naa', 'nab', 'nac', 'nae', 'naf', 'nag', 'nah', 'nai', 'naj', 'nak', 'nal', 'nam', 'nan', 'nao', 'nap', 'naq', 'nar', 'nas', 'nat', 'naw', 'nax', 'nay', 'naz', 'nb', 'nba', 'nbb', 'nbc', 'nbd', 'nbe', 'nbg', 'nbh', 'nbi', 'nbj', 'nbk', 'nbm', 'nbn', 'nbo', 'nbp', 'nbq', 'nbr', 'nbs', 'nbt', 'nbu', 'nbv', 'nbw', 'nby', 'nca', 'ncb', 'ncc', 'ncd', 'nce', 'ncf', 'ncg', 'nch', 'nci', 'ncj', 'nck', 'ncl', 'ncm', 'ncn', 'nco', 'ncp', 'ncr', 'ncs', 'nct', 'ncu', 'ncx', 'ncz', 'nd', 'nda', 'ndb', 'ndc', 'ndd', 'ndf', 'ndg', 'ndh', 'ndi', 'ndj', 'ndk', 'ndl', 'ndm', 'ndn', 'ndp', 'ndq', 'ndr', 'nds', 'ndt', 'ndu', 'ndv', 'ndw', 'ndx', 'ndy', 'ndz', 'ne', 'nea', 'neb', 'nec', 'ned', 'nee', 'nef', 'neg', 'neh', 'nei', 'nej', 'nek', 'nem', 'nen', 'neo', 'neq', 'ner', 'nes', 'net', 'neu', 'nev', 'new', 'nex', 'ney', 'nez', 'nfa', 'nfd', 'nfl', 'nfr', 'nfu', 'ng', 'nga', 'ngb', 'ngc', 'ngd', 'nge', 'ngg', 'ngh', 'ngi', 'ngj', 'ngk', 'ngl', 'ngm', 'ngn', 'ngo', 'ngp', 'ngq', 'ngr', 'ngs', 'ngt', 'ngu', 'ngv', 'ngw', 'ngx', 'ngy', 'ngz', 'nha', 'nhb', 'nhc', 'nhd', 'nhe', 'nhf', 'nhg', 'nhh', 'nhi', 'nhk', 'nhm', 'nhn', 'nho', 'nhp', 'nhq', 'nhr', 'nht', 'nhu', 'nhv', 'nhw', 'nhx', 'nhy', 'nhz', 'nia', 'nib', 'nic', 'nid', 'nie', 'nif', 'nig', 'nih', 'nii', 'nij', 'nik', 'nil', 'nim', 'nin', 'nio', 'niq', 'nir', 'nis', 'nit', 'niu', 'niv', 'niw', 'nix', 'niy', 'niz', 'nja', 'njb', 'njd', 'njh', 'nji', 'njj', 'njl', 'njm', 'njn', 'njo', 'njr', 'njs', 'njt', 'nju', 'njx', 'njy', 'njz', 'nka', 'nkb', 'nkc', 'nkd', 'nke', 'nkf', 'nkg', 'nkh', 'nki', 'nkj', 'nkk', 'nkm', 'nkn', 'nko', 'nkp', 'nkq', 'nkr', 'nks', 'nkt', 'nku', 'nkv', 'nkw', 'nkx', 'nkz', 'nl', 'nla', 'nlc', 'nle', 'nlg', 'nli', 'nlj', 'nlk', 'nll', 'nlo', 'nlq', 'nlu', 'nlv', 'nlw', 'nlx', 'nly', 'nlz', 'nma', 'nmb', 'nmc', 'nmd', 'nme', 'nmf', 'nmg', 'nmh', 'nmi', 'nmj', 'nmk', 'nml', 'nmm', 'nmn', 'nmo', 'nmp', 'nmq', 'nmr', 'nms', 'nmt', 'nmu', 'nmv', 'nmw', 'nmx', 'nmy', 'nmz', 'nn', 'nna', 'nnb', 'nnc', 'nnd', 'nne', 'nnf', 'nng', 'nnh', 'nni', 'nnj', 'nnk', 'nnl', 'nnm', 'nnn', 'nnp', 'nnq', 'nnr', 'nns', 'nnt', 'nnu', 'nnv', 'nnw', 'nny', 'nnz', 'no', 'noa', 'noc', 'nod', 'noe', 'nof', 'nog', 'noh', 'noi', 'noj', 'nok', 'nol', 'nom', 'non', 'nop', 'noq', 'nos', 'not', 'nou', 'nov', 'now', 'noy', 'noz', 'npa', 'npb', 'npg', 'nph', 'npi', 'npl', 'npn', 'npo', 'nps', 'npu', 'npy', 'nqg', 'nqk', 'nqm', 'nqn', 'nqo', 'nqq', 'nqy', 'nr', 'nra', 'nrb', 'nrc', 'nre', 'nrf', 'nrg', 'nri', 'nrk', 'nrl', 'nrm', 'nrn', 'nrp', 'nrr', 'nrt', 'nru', 'nrx', 'nrz', 'nsa', 'nsc', 'nsd', 'nse', 'nsf', 'nsg', 'nsh', 'nsi', 'nsk', 'nsl', 'nsm', 'nsn', 'nso', 'nsp', 'nsq', 'nsr', 'nss', 'nst', 'nsu', 'nsv', 'nsw', 'nsx', 'nsy', 'nsz', 'ntd', 'nte', 'ntg', 'nti', 'ntj', 'ntk', 'ntm', 'nto', 'ntp', 'ntr', 'ntu', 'ntw', 'ntx', 'nty', 'ntz', 'nua', 'nub', 'nuc', 'nud', 'nue', 'nuf', 'nug', 'nuh', 'nui', 'nuj', 'nuk', 'nul', 'num', 'nun', 'nuo', 'nup', 'nuq', 'nur', 'nus', 'nut', 'nuu', 'nuv', 'nuw', 'nux', 'nuy', 'nuz', 'nv', 'nvh', 'nvm', 'nvo', 'nwa', 'nwb', 'nwc', 'nwe', 'nwg', 'nwi', 'nwm', 'nwo', 'nwr', 'nwx', 'nwy', 'nxa', 'nxd', 'nxe', 'nxg', 'nxi', 'nxk', 'nxl', 'nxm', 'nxn', 'nxo', 'nxq', 'nxr', 'nxu', 'nxx', 'ny', 'nyb', 'nyc', 'nyd', 'nye', 'nyf', 'nyg', 'nyh', 'nyi', 'nyj', 'nyk', 'nyl', 'nym', 'nyn', 'nyo', 'nyp', 'nyq', 'nyr', 'nys', 'nyt', 'nyu', 'nyv', 'nyw', 'nyx', 'nyy', 'nza', 'nzb', 'nzi', 'nzk', 'nzm', 'nzs', 'nzu', 'nzy', 'nzz', 'oaa', 'oac', 'oar', 'oav', 'obi', 'obk', 'obl', 'obm', 'obo', 'obr', 'obt', 'obu', 'oc', 'oca', 'och', 'oco', 'ocu', 'oda', 'odk', 'odt', 'odu', 'ofo', 'ofs', 'ofu', 'ogb', 'ogc', 'oge', 'ogg', 'ogo', 'ogu', 'oht', 'ohu', 'oia', 'oin', 'oj', 'ojb', 'ojc', 'ojg', 'ojp', 'ojs', 'ojv', 'ojw', 'oka', 'okb', 'okd', 'oke', 'okg', 'okh', 'oki', 'okj', 'okk', 'okl', 'okm', 'okn', 'oko', 'okr', 'oks', 'oku', 'okv', 'okx', 'ola', 'old', 'ole', 'olk', 'olm', 'olo', 'olr', 'olt', 'olu', 'om', 'oma', 'omb', 'omc', 'omg', 'omi', 'omk', 'oml', 'omn', 'omo', 'omp', 'omr', 'omt', 'omu', 'omw', 'omx', 'ona', 'onb', 'one', 'ong', 'oni', 'onj', 'onk', 'onn', 'ono', 'onp', 'onr', 'ons', 'ont', 'onu', 'onw', 'onx', 'ood', 'oog', 'oon', 'oor', 'oos', 'opa', 'opk', 'opm', 'opo', 'opt', 'opy', 'or', 'ora', 'orc', 'ore', 'org', 'orh', 'orn', 'oro', 'orr', 'ors', 'ort', 'oru', 'orv', 'orw', 'orx', 'ory', 'orz', 'os', 'osa', 'osc', 'osi', 'oso', 'osp', 'ost', 'osu', 'osx', 'ota', 'otb', 'otd', 'ote', 'oti', 'otk', 'otl', 'otm', 'otn', 'oto', 'otq', 'otr', 'ots', 'ott', 'otu', 'otw', 'otx', 'oty', 'otz', 'oua', 'oub', 'oue', 'oui', 'oum', 'owi', 'owl', 'oyb', 'oyd', 'oym', 'oyy', 'ozm', 'pa', 'paa', 'pab', 'pac', 'pad', 'pae', 'paf', 'pag', 'pah', 'pai', 'pak', 'pal', 'pam', 'pao', 'pap', 'paq', 'par', 'pas', 'pat', 'pau', 'pav', 'paw', 'pax', 'pay', 'paz', 'pbb', 'pbc', 'pbe', 'pbf', 'pbg', 'pbh', 'pbi', 'pbl', 'pbn', 'pbo', 'pbp', 'pbr', 'pbs', 'pbt', 'pbu', 'pbv', 'pby', 'pca', 'pcb', 'pcc', 'pcd', 'pce', 'pcf', 'pcg', 'pch', 'pci', 'pcj', 'pck', 'pcl', 'pcm', 'pcn', 'pcp', 'pcw', 'pda', 'pdc', 'pdi', 'pdn', 'pdo', 'pdt', 'pdu', 'pea', 'peb', 'ped', 'pee', 'pef', 'peg', 'peh', 'pei', 'pej', 'pek', 'pel', 'pem', 'peo', 'pep', 'peq', 'pes', 'pev', 'pex', 'pey', 'pez', 'pfa', 'pfe', 'pfl', 'pga', 'pgd', 'pgg', 'pgi', 'pgk', 'pgl', 'pgn', 'pgs', 'pgu', 'pgz', 'pha', 'phd', 'phg', 'phh', 'phi', 'phk', 'phl', 'phm', 'phn', 'pho', 'phq', 'phr', 'pht', 'phu', 'phv', 'phw', 'pi', 'pia', 'pib', 'pic', 'pid', 'pie', 'pif', 'pig', 'pih', 'pii', 'pij', 'pil', 'pim', 'pin', 'pio', 'pip', 'pir', 'pis', 'pit', 'piu', 'piv', 'piw', 'pix', 'piy', 'piz', 'pjt', 'pka', 'pkb', 'pkc', 'pkg', 'pkh', 'pkn', 'pko', 'pkp', 'pkr', 'pks', 'pkt', 'pku', 'pl', 'pla', 'plb', 'plc', 'pld', 'ple', 'plg', 'plh', 'plj', 'plk', 'pll', 'pln', 'plo', 'plp', 'plq', 'plr', 'pls', 'plt', 'plu', 'plv', 'plw', 'ply', 'plz', 'pma', 'pmb', 'pmd', 'pme', 'pmf', 'pmh', 'pmi', 'pmj', 'pmk', 'pml', 'pmm', 'pmn', 'pmo', 'pmq', 'pmr', 'pms', 'pmt', 'pmw', 'pmx', 'pmy', 'pmz', 'pna', 'pnb', 'pnc', 'pne', 'png', 'pnh', 'pni', 'pnj', 'pnk', 'pnl', 'pnm', 'pnn', 'pno', 'pnp', 'pnq', 'pnr', 'pns', 'pnt', 'pnu', 'pnv', 'pnw', 'pnx', 'pny', 'pnz', 'poc', 'poe', 'pof', 'pog', 'poh', 'poi', 'pok', 'pom', 'pon', 'poo', 'pop', 'poq', 'pos', 'pot', 'pov', 'pow', 'pox', 'poy', 'ppe', 'ppi', 'ppk', 'ppl', 'ppm', 'ppn', 'ppo', 'ppp', 'ppq', 'pps', 'ppt', 'ppu', 'pqa', 'pqm', 'pra', 'prb', 'prc', 'prd', 'pre', 'prf', 'prg', 'prh', 'pri', 'prk', 'prl', 'prm', 'prn', 'pro', 'prp', 'prq', 'prr', 'prs', 'prt', 'pru', 'prw', 'prx', 'prz', 'ps', 'psa', 'psc', 'psd', 'pse', 'psg', 'psh', 'psi', 'psl', 'psm', 'psn', 'pso', 'psp', 'psq', 'psr', 'pss', 'pst', 'psu', 'psw', 'psy', 'pt', 'pta', 'pth', 'pti', 'ptn', 'pto', 'ptp', 'ptq', 'ptr', 'ptt', 'ptu', 'ptv', 'ptw', 'pty', 'pua', 'pub', 'puc', 'pud', 'pue', 'puf', 'pug', 'pui', 'puj', 'puk', 'pum', 'puo', 'pup', 'puq', 'pur', 'put', 'puu', 'puw', 'pux', 'puy', 'pwa', 'pwb', 'pwg', 'pwi', 'pwm', 'pwn', 'pwo', 'pwr', 'pww', 'pxm', 'pye', 'pym', 'pyn', 'pys', 'pyu', 'pyx', 'pyy', 'pzn', 'qaa-qtz', 'qu', 'qua', 'qub', 'quc', 'qud', 'quf', 'qug', 'quh', 'qui', 'quk', 'qul', 'qum', 'qun', 'qup', 'quq', 'qur', 'qus', 'quv', 'quw', 'qux', 'quy', 'quz', 'qva', 'qvc', 'qve', 'qvh', 'qvi', 'qvj', 'qvl', 'qvm', 'qvn', 'qvo', 'qvp', 'qvs', 'qvw', 'qvy', 'qvz', 'qwa', 'qwc', 'qwh', 'qwm', 'qws', 'qwt', 'qxa', 'qxc', 'qxh', 'qxl', 'qxn', 'qxo', 'qxp', 'qxq', 'qxr', 'qxs', 'qxt', 'qxu', 'qxw', 'qya', 'qyp', 'raa', 'rab', 'rac', 'rad', 'raf', 'rag', 'rah', 'rai', 'raj', 'rak', 'ral', 'ram', 'ran', 'rao', 'rap', 'raq', 'rar', 'ras', 'rat', 'rau', 'rav', 'raw', 'rax', 'ray', 'raz', 'rbb', 'rbk', 'rbl', 'rbp', 'rcf', 'rdb', 'rea', 'reb', 'ree', 'reg', 'rei', 'rej', 'rel', 'rem', 'ren', 'rer', 'res', 'ret', 'rey', 'rga', 'rge', 'rgk', 'rgn', 'rgr', 'rgs', 'rgu', 'rhg', 'rhp', 'ria', 'rie', 'rif', 'ril', 'rim', 'rin', 'rir', 'rit', 'riu', 'rjg', 'rji', 'rjs', 'rka', 'rkb', 'rkh', 'rki', 'rkm', 'rkt', 'rkw', 'rm', 'rma', 'rmb', 'rmc', 'rmd', 'rme', 'rmf', 'rmg', 'rmh', 'rmi', 'rmk', 'rml', 'rmm', 'rmn', 'rmo', 'rmp', 'rmq', 'rms', 'rmt', 'rmu', 'rmv', 'rmw', 'rmx', 'rmy', 'rmz', 'rn', 'rnd', 'rng', 'rnl', 'rnn', 'rnp', 'rnr', 'rnw', 'ro', 'roa', 'rob', 'roc', 'rod', 'roe', 'rof', 'rog', 'rol', 'rom', 'roo', 'rop', 'ror', 'rou', 'row', 'rpn', 'rpt', 'rri', 'rro', 'rrt', 'rsb', 'rsi', 'rsl', 'rsm', 'rtc', 'rth', 'rtm', 'rts', 'rtw', 'ru', 'rub', 'ruc', 'rue', 'ruf', 'rug', 'ruh', 'rui', 'ruk', 'ruo', 'rup', 'ruq', 'rut', 'ruu', 'ruy', 'ruz', 'rw', 'rwa', 'rwk', 'rwm', 'rwo', 'rwr', 'rxd', 'rxw', 'ryn', 'rys', 'ryu', 'rzh', 'sa', 'saa', 'sab', 'sac', 'sad', 'sae', 'saf', 'sah', 'sai', 'saj', 'sak', 'sal', 'sam', 'sao', 'saq', 'sar', 'sas', 'sat', 'sau', 'sav', 'saw', 'sax', 'say', 'saz', 'sba', 'sbb', 'sbc', 'sbd', 'sbe', 'sbf', 'sbg', 'sbh', 'sbi', 'sbj', 'sbk', 'sbl', 'sbm', 'sbn', 'sbo', 'sbp', 'sbq', 'sbr', 'sbs', 'sbt', 'sbu', 'sbv', 'sbw', 'sbx', 'sby', 'sbz', 'sc', 'scb', 'sce', 'scf', 'scg', 'sch', 'sci', 'sck', 'scl', 'scn', 'sco', 'scp', 'scq', 'scs', 'scu', 'scv', 'scw', 'scx', 'sd', 'sda', 'sdb', 'sdc', 'sde', 'sdf', 'sdg', 'sdh', 'sdj', 'sdk', 'sdl', 'sdm', 'sdn', 'sdo', 'sdp', 'sdr', 'sds', 'sdt', 'sdu', 'sdx', 'sdz', 'se', 'sea', 'seb', 'sec', 'sed', 'see', 'sef', 'seg', 'seh', 'sei', 'sej', 'sek', 'sel', 'sem', 'sen', 'seo', 'sep', 'seq', 'ser', 'ses', 'set', 'seu', 'sev', 'sew', 'sey', 'sez', 'sfb', 'sfe', 'sfm', 'sfs', 'sfw', 'sg', 'sga', 'sgb', 'sgc', 'sgd', 'sge', 'sgg', 'sgh', 'sgi', 'sgj', 'sgk', 'sgm', 'sgn', 'sgp', 'sgr', 'sgs', 'sgt', 'sgu', 'sgw', 'sgx', 'sgy', 'sgz', 'sh', 'sha', 'shb', 'shc', 'shd', 'she', 'shg', 'shh', 'shi', 'shj', 'shk', 'shl', 'shm', 'shn', 'sho', 'shp', 'shq', 'shr', 'shs', 'sht', 'shu', 'shv', 'shw', 'shx', 'shy', 'shz', 'si', 'sia', 'sib', 'sid', 'sie', 'sif', 'sig', 'sih', 'sii', 'sij', 'sik', 'sil', 'sim', 'sio', 'sip', 'siq', 'sir', 'sis', 'sit', 'siu', 'siv', 'siw', 'six', 'siy', 'siz', 'sja', 'sjb', 'sjd', 'sje', 'sjg', 'sjk', 'sjl', 'sjm', 'sjn', 'sjo', 'sjp', 'sjr', 'sjs', 'sjt', 'sju', 'sjw', 'sk', 'ska', 'skb', 'skc', 'skd', 'ske', 'skf', 'skg', 'skh', 'ski', 'skj', 'skk', 'skm', 'skn', 'sko', 'skp', 'skq', 'skr', 'sks', 'skt', 'sku', 'skv', 'skw', 'skx', 'sky', 'skz', 'sl', 'sla', 'slc', 'sld', 'sle', 'slf', 'slg', 'slh', 'sli', 'slj', 'sll', 'slm', 'sln', 'slp', 'slq', 'slr', 'sls', 'slt', 'slu', 'slw', 'slx', 'sly', 'slz', 'sm', 'sma', 'smb', 'smc', 'smd', 'smf', 'smg', 'smh', 'smi', 'smj', 'smk', 'sml', 'smm', 'smn', 'smp', 'smq', 'smr', 'sms', 'smt', 'smu', 'smv', 'smw', 'smx', 'smy', 'smz', 'sn', 'snb', 'snc', 'sne', 'snf', 'sng', 'snh', 'sni', 'snj', 'snk', 'snl', 'snm', 'snn', 'sno', 'snp', 'snq', 'snr', 'sns', 'snu', 'snv', 'snw', 'snx', 'sny', 'snz', 'so', 'soa', 'sob', 'soc', 'sod', 'soe', 'sog', 'soh', 'soi', 'soj', 'sok', 'sol', 'son', 'soo', 'sop', 'soq', 'sor', 'sos', 'sou', 'sov', 'sow', 'sox', 'soy', 'soz', 'spb', 'spc', 'spd', 'spe', 'spg', 'spi', 'spk', 'spl', 'spm', 'spn', 'spo', 'spp', 'spq', 'spr', 'sps', 'spt', 'spu', 'spv', 'spx', 'spy', 'sq', 'sqa', 'sqh', 'sqk', 'sqm', 'sqn', 'sqo', 'sqq', 'sqr', 'sqs', 'sqt', 'squ', 'sr', 'sra', 'srb', 'src', 'sre', 'srf', 'srg', 'srh', 'sri', 'srk', 'srl', 'srm', 'srn', 'sro', 'srq', 'srr', 'srs', 'srt', 'sru', 'srv', 'srw', 'srx', 'sry', 'srz', 'ss', 'ssa', 'ssb', 'ssc', 'ssd', 'sse', 'ssf', 'ssg', 'ssh', 'ssi', 'ssj', 'ssk', 'ssl', 'ssm', 'ssn', 'sso', 'ssp', 'ssq', 'ssr', 'sss', 'sst', 'ssu', 'ssv', 'ssx', 'ssy', 'ssz', 'st', 'sta', 'stb', 'std', 'ste', 'stf', 'stg', 'sth', 'sti', 'stj', 'stk', 'stl', 'stm', 'stn', 'sto', 'stp', 'stq', 'str', 'sts', 'stt', 'stu', 'stv', 'stw', 'sty', 'su', 'sua', 'sub', 'suc', 'sue', 'sug', 'sui', 'suj', 'suk', 'suq', 'sur', 'sus', 'sut', 'suv', 'suw', 'sux', 'suy', 'suz', 'sv', 'sva', 'svb', 'svc', 'sve', 'svk', 'svm', 'svs', 'svx', 'sw', 'swb', 'swc', 'swf', 'swg', 'swh', 'swi', 'swj', 'swk', 'swl', 'swm', 'swn', 'swo', 'swp', 'swq', 'swr', 'sws', 'swt', 'swu', 'swv', 'sww', 'swx', 'swy', 'sxb', 'sxc', 'sxe', 'sxg', 'sxk', 'sxl', 'sxm', 'sxn', 'sxo', 'sxr', 'sxs', 'sxu', 'sxw', 'sya', 'syb', 'syc', 'syi', 'syk', 'syl', 'sym', 'syn', 'syo', 'syr', 'sys', 'syw', 'syx', 'syy', 'sza', 'szb', 'szc', 'szd', 'sze', 'szg', 'szl', 'szn', 'szp', 'szv', 'szw', 'ta', 'taa', 'tab', 'tac', 'tad', 'tae', 'taf', 'tag', 'tai', 'taj', 'tak', 'tal', 'tan', 'tao', 'tap', 'taq', 'tar', 'tas', 'tau', 'tav', 'taw', 'tax', 'tay', 'taz', 'tba', 'tbb', 'tbc', 'tbd', 'tbe', 'tbf', 'tbg', 'tbh', 'tbi', 'tbj', 'tbk', 'tbl', 'tbm', 'tbn', 'tbo', 'tbp', 'tbr', 'tbs', 'tbt', 'tbu', 'tbv', 'tbw', 'tbx', 'tby', 'tbz', 'tca', 'tcb', 'tcc', 'tcd', 'tce', 'tcf', 'tcg', 'tch', 'tci', 'tck', 'tcl', 'tcm', 'tcn', 'tco', 'tcp', 'tcq', 'tcs', 'tct', 'tcu', 'tcw', 'tcx', 'tcy', 'tcz', 'tda', 'tdb', 'tdc', 'tdd', 'tde', 'tdf', 'tdg', 'tdh', 'tdi', 'tdj', 'tdk', 'tdl', 'tdm', 'tdn', 'tdo', 'tdq', 'tdr', 'tds', 'tdt', 'tdv', 'tdx', 'tdy', 'te', 'tea', 'teb', 'tec', 'ted', 'tee', 'tef', 'teg', 'teh', 'tei', 'tek', 'tem', 'ten', 'teo', 'tep', 'teq', 'ter', 'tes', 'tet', 'teu', 'tev', 'tew', 'tex', 'tey', 'tfi', 'tfn', 'tfo', 'tfr', 'tft', 'tg', 'tga', 'tgb', 'tgc', 'tgd', 'tge', 'tgf', 'tgh', 'tgi', 'tgj', 'tgn', 'tgo', 'tgp', 'tgq', 'tgr', 'tgs', 'tgt', 'tgu', 'tgv', 'tgw', 'tgx', 'tgy', 'tgz', 'th', 'thd', 'the', 'thf', 'thh', 'thi', 'thk', 'thl', 'thm', 'thn', 'thp', 'thq', 'thr', 'ths', 'tht', 'thu', 'thv', 'thw', 'thy', 'thz', 'ti', 'tia', 'tic', 'tif', 'tig', 'tih', 'tii', 'tij', 'tik', 'til', 'tim', 'tin', 'tio', 'tip', 'tiq', 'tis', 'tit', 'tiu', 'tiv', 'tiw', 'tix', 'tiy', 'tiz', 'tja', 'tjg', 'tji', 'tjl', 'tjm', 'tjn', 'tjo', 'tjs', 'tju', 'tjw', 'tk', 'tka', 'tkb', 'tkd', 'tke', 'tkf', 'tkg', 'tkl', 'tkm', 'tkn', 'tkp', 'tkq', 'tkr', 'tks', 'tkt', 'tku', 'tkv', 'tkw', 'tkx', 'tkz', 'tl', 'tla', 'tlb', 'tlc', 'tld', 'tlf', 'tlg', 'tlh', 'tli', 'tlj', 'tlk', 'tll', 'tlm', 'tln', 'tlo', 'tlp', 'tlq', 'tlr', 'tls', 'tlt', 'tlu', 'tlv', 'tlx', 'tly', 'tma', 'tmb', 'tmc', 'tmd', 'tme', 'tmf', 'tmg', 'tmh', 'tmi', 'tmj', 'tmk', 'tml', 'tmm', 'tmn', 'tmo', 'tmq', 'tmr', 'tms', 'tmt', 'tmu', 'tmv', 'tmw', 'tmy', 'tmz', 'tn', 'tna', 'tnb', 'tnc', 'tnd', 'tne', 'tng', 'tnh', 'tni', 'tnk', 'tnl', 'tnm', 'tnn', 'tno', 'tnp', 'tnq', 'tnr', 'tns', 'tnt', 'tnu', 'tnv', 'tnw', 'tnx', 'tny', 'tnz', 'to', 'tob', 'toc', 'tod', 'tof', 'tog', 'toh', 'toi', 'toj', 'tol', 'tom', 'too', 'top', 'toq', 'tor', 'tos', 'tou', 'tov', 'tow', 'tox', 'toy', 'toz', 'tpa', 'tpc', 'tpe', 'tpf', 'tpg', 'tpi', 'tpj', 'tpk', 'tpl', 'tpm', 'tpn', 'tpo', 'tpp', 'tpq', 'tpr', 'tpt', 'tpu', 'tpv', 'tpw', 'tpx', 'tpy', 'tpz', 'tqb', 'tql', 'tqm', 'tqn', 'tqo', 'tqp', 'tqq', 'tqr', 'tqt', 'tqu', 'tqw', 'tr', 'tra', 'trb', 'trc', 'trd', 'tre', 'trf', 'trg', 'trh', 'tri', 'trj', 'trl', 'trm', 'trn', 'tro', 'trp', 'trq', 'trr', 'trs', 'trt', 'tru', 'trv', 'trw', 'trx', 'try', 'trz', 'ts', 'tsa', 'tsb', 'tsc', 'tsd', 'tse', 'tsg', 'tsh', 'tsi', 'tsj', 'tsk', 'tsl', 'tsm', 'tsp', 'tsq', 'tsr', 'tss', 'tst', 'tsu', 'tsv', 'tsw', 'tsx', 'tsy', 'tsz', 'tt', 'tta', 'ttb', 'ttc', 'ttd', 'tte', 'ttf', 'ttg', 'tth', 'tti', 'ttj', 'ttk', 'ttl', 'ttm', 'ttn', 'tto', 'ttp', 'ttq', 'ttr', 'tts', 'ttt', 'ttu', 'ttv', 'ttw', 'tty', 'ttz', 'tua', 'tub', 'tuc', 'tud', 'tue', 'tuf', 'tug', 'tuh', 'tui', 'tuj', 'tul', 'tum', 'tun', 'tuo', 'tup', 'tuq', 'tus', 'tut', 'tuu', 'tuv', 'tux', 'tuy', 'tuz', 'tva', 'tvd', 'tve', 'tvk', 'tvl', 'tvm', 'tvn', 'tvo', 'tvs', 'tvt', 'tvu', 'tvw', 'tvy', 'tw', 'twa', 'twb', 'twc', 'twd', 'twe', 'twf', 'twg', 'twh', 'twl', 'twm', 'twn', 'two', 'twp', 'twq', 'twr', 'twt', 'twu', 'tww', 'twx', 'twy', 'txa', 'txb', 'txc', 'txe', 'txg', 'txh', 'txi', 'txj', 'txm', 'txn', 'txo', 'txq', 'txr', 'txs', 'txt', 'txu', 'txx', 'txy', 'ty', 'tya', 'tye', 'tyh', 'tyi', 'tyj', 'tyl', 'tyn', 'typ', 'tyr', 'tys', 'tyt', 'tyu', 'tyv', 'tyx', 'tyz', 'tza', 'tzh', 'tzj', 'tzl', 'tzm', 'tzn', 'tzo', 'tzx', 'uam', 'uan', 'uar', 'uba', 'ubi', 'ubl', 'ubr', 'ubu', 'uby', 'uda', 'ude', 'udg', 'udi', 'udj', 'udl', 'udm', 'udu', 'ues', 'ufi', 'ug', 'uga', 'ugb', 'uge', 'ugn', 'ugo', 'ugy', 'uha', 'uhn', 'uis', 'uiv', 'uji', 'uk', 'uka', 'ukg', 'ukh', 'ukl', 'ukp', 'ukq', 'uks', 'uku', 'ukw', 'uky', 'ula', 'ulb', 'ulc', 'ule', 'ulf', 'uli', 'ulk', 'ull', 'ulm', 'uln', 'ulu', 'ulw', 'uma', 'umb', 'umc', 'umd', 'umg', 'umi', 'umm', 'umn', 'umo', 'ump', 'umr', 'ums', 'umu', 'una', 'und', 'une', 'ung', 'unk', 'unm', 'unn', 'unr', 'unu', 'unx', 'unz', 'upi', 'upv', 'ur', 'ura', 'urb', 'urc', 'ure', 'urf', 'urg', 'urh', 'uri', 'urk', 'url', 'urm', 'urn', 'uro', 'urp', 'urr', 'urt', 'uru', 'urv', 'urw', 'urx', 'ury', 'urz', 'usa', 'ush', 'usi', 'usk', 'usp', 'usu', 'uta', 'ute', 'utp', 'utr', 'utu', 'uum', 'uun', 'uur', 'uuu', 'uve', 'uvh', 'uvl', 'uwa', 'uya', 'uz', 'uzn', 'uzs', 'vaa', 'vae', 'vaf', 'vag', 'vah', 'vai', 'vaj', 'val', 'vam', 'van', 'vao', 'vap', 'var', 'vas', 'vau', 'vav', 'vay', 'vbb', 'vbk', 've', 'vec', 'ved', 'vel', 'vem', 'veo', 'vep', 'ver', 'vgr', 'vgt', 'vi', 'vic', 'vid', 'vif', 'vig', 'vil', 'vin', 'vis', 'vit', 'viv', 'vka', 'vki', 'vkj', 'vkk', 'vkl', 'vkm', 'vko', 'vkp', 'vkt', 'vku', 'vlp', 'vls', 'vma', 'vmb', 'vmc', 'vmd', 'vme', 'vmf', 'vmg', 'vmh', 'vmi', 'vmj', 'vmk', 'vml', 'vmm', 'vmp', 'vmq', 'vmr', 'vms', 'vmu', 'vmv', 'vmw', 'vmx', 'vmy', 'vmz', 'vnk', 'vnm', 'vnp', 'vo', 'vor', 'vot', 'vra', 'vro', 'vrs', 'vrt', 'vsi', 'vsl', 'vsv', 'vto', 'vum', 'vun', 'vut', 'vwa', 'wa', 'waa', 'wab', 'wac', 'wad', 'wae', 'waf', 'wag', 'wah', 'wai', 'waj', 'wak', 'wal', 'wam', 'wan', 'wao', 'wap', 'waq', 'war', 'was', 'wat', 'wau', 'wav', 'waw', 'wax', 'way', 'waz', 'wba', 'wbb', 'wbe', 'wbf', 'wbh', 'wbi', 'wbj', 'wbk', 'wbl', 'wbm', 'wbp', 'wbq', 'wbr', 'wbt', 'wbv', 'wbw', 'wca', 'wci', 'wdd', 'wdg', 'wdj', 'wdk', 'wdu', 'wdy', 'wea', 'wec', 'wed', 'weg', 'weh', 'wei', 'wem', 'wen', 'weo', 'wep', 'wer', 'wes', 'wet', 'weu', 'wew', 'wfg', 'wga', 'wgb', 'wgg', 'wgi', 'wgo', 'wgu', 'wgy', 'wha', 'whg', 'whk', 'whu', 'wib', 'wic', 'wie', 'wif', 'wig', 'wih', 'wii', 'wij', 'wik', 'wil', 'wim', 'win', 'wir', 'wiu', 'wiv', 'wiy', 'wja', 'wji', 'wka', 'wkb', 'wkd', 'wkl', 'wku', 'wkw', 'wky', 'wla', 'wlc', 'wle', 'wlg', 'wli', 'wlk', 'wll', 'wlm', 'wlo', 'wlr', 'wls', 'wlu', 'wlv', 'wlw', 'wlx', 'wly', 'wma', 'wmb', 'wmc', 'wmd', 'wme', 'wmh', 'wmi', 'wmm', 'wmn', 'wmo', 'wms', 'wmt', 'wmw', 'wmx', 'wnb', 'wnc', 'wnd', 'wne', 'wng', 'wni', 'wnk', 'wnm', 'wnn', 'wno', 'wnp', 'wnu', 'wnw', 'wny', 'wo', 'woa', 'wob', 'woc', 'wod', 'woe', 'wof', 'wog', 'woi', 'wok', 'wom', 'won', 'woo', 'wor', 'wos', 'wow', 'woy', 'wpc', 'wra', 'wrb', 'wrd', 'wrg', 'wrh', 'wri', 'wrk', 'wrl', 'wrm', 'wrn', 'wro', 'wrp', 'wrr', 'wrs', 'wru', 'wrv', 'wrw', 'wrx', 'wry', 'wrz', 'wsa', 'wsg', 'wsi', 'wsk', 'wsr', 'wss', 'wsu', 'wsv', 'wtf', 'wth', 'wti', 'wtk', 'wtm', 'wtw', 'wua', 'wub', 'wud', 'wuh', 'wul', 'wum', 'wun', 'wur', 'wut', 'wuu', 'wuv', 'wux', 'wuy', 'wwa', 'wwb', 'wwo', 'wwr', 'www', 'wxa', 'wxw', 'wya', 'wyb', 'wyi', 'wym', 'wyr', 'wyy', 'xaa', 'xab', 'xac', 'xad', 'xae', 'xag', 'xai', 'xaj', 'xak', 'xal', 'xam', 'xan', 'xao', 'xap', 'xaq', 'xar', 'xas', 'xat', 'xau', 'xav', 'xaw', 'xay', 'xbb', 'xbc', 'xbd', 'xbe', 'xbg', 'xbi', 'xbj', 'xbm', 'xbn', 'xbo', 'xbp', 'xbr', 'xbw', 'xby', 'xcb', 'xcc', 'xce', 'xcg', 'xch', 'xcl', 'xcm', 'xcn', 'xco', 'xcr', 'xct', 'xcu', 'xcv', 'xcw', 'xcy', 'xda', 'xdc', 'xdk', 'xdm', 'xdy', 'xeb', 'xed', 'xeg', 'xel', 'xem', 'xep', 'xer', 'xes', 'xet', 'xeu', 'xfa', 'xga', 'xgb', 'xgd', 'xgf', 'xgg', 'xgi', 'xgl', 'xgm', 'xgr', 'xgu', 'xgw', 'xh', 'xha', 'xhc', 'xhd', 'xhe', 'xhr', 'xht', 'xhu', 'xhv', 'xib', 'xii', 'xil', 'xin', 'xir', 'xis', 'xiv', 'xiy', 'xjb', 'xjt', 'xka', 'xkb', 'xkc', 'xkd', 'xke', 'xkf', 'xkg', 'xki', 'xkj', 'xkk', 'xkl', 'xkn', 'xko', 'xkp', 'xkq', 'xkr', 'xks', 'xkt', 'xku', 'xkv', 'xkw', 'xkx', 'xky', 'xkz', 'xla', 'xlb', 'xlc', 'xld', 'xle', 'xlg', 'xli', 'xln', 'xlo', 'xlp', 'xls', 'xlu', 'xly', 'xma', 'xmb', 'xmc', 'xmd', 'xme', 'xmf', 'xmg', 'xmh', 'xmj', 'xmk', 'xml', 'xmm', 'xmn', 'xmo', 'xmp', 'xmq', 'xmr', 'xms', 'xmt', 'xmu', 'xmv', 'xmw', 'xmx', 'xmy', 'xmz', 'xna', 'xnb', 'xng', 'xnh', 'xni', 'xnk', 'xnn', 'xno', 'xnr', 'xns', 'xnt', 'xnu', 'xny', 'xnz', 'xoc', 'xod', 'xog', 'xoi', 'xok', 'xom', 'xon', 'xoo', 'xop', 'xor', 'xow', 'xpa', 'xpc', 'xpe', 'xpg', 'xpi', 'xpj', 'xpk', 'xpm', 'xpn', 'xpo', 'xpp', 'xpq', 'xpr', 'xps', 'xpt', 'xpu', 'xpy', 'xqa', 'xqt', 'xra', 'xrb', 'xrd', 'xre', 'xrg', 'xri', 'xrm', 'xrn', 'xrq', 'xrr', 'xrt', 'xru', 'xrw', 'xsa', 'xsb', 'xsc', 'xsd', 'xse', 'xsh', 'xsi', 'xsl', 'xsm', 'xsn', 'xso', 'xsp', 'xsq', 'xsr', 'xss', 'xsu', 'xsv', 'xsy', 'xta', 'xtb', 'xtc', 'xtd', 'xte', 'xtg', 'xth', 'xti', 'xtj', 'xtl', 'xtm', 'xtn', 'xto', 'xtp', 'xtq', 'xtr', 'xts', 'xtt', 'xtu', 'xtv', 'xtw', 'xty', 'xtz', 'xua', 'xub', 'xud', 'xug', 'xuj', 'xul', 'xum', 'xun', 'xuo', 'xup', 'xur', 'xut', 'xuu', 'xve', 'xvi', 'xvn', 'xvo', 'xvs', 'xwa', 'xwc', 'xwd', 'xwe', 'xwg', 'xwj', 'xwk', 'xwl', 'xwo', 'xwr', 'xwt', 'xww', 'xxb', 'xxk', 'xxm', 'xxr', 'xxt', 'xya', 'xyb', 'xyj', 'xyk', 'xyl', 'xyt', 'xyy', 'xzh', 'xzm', 'xzp', 'yaa', 'yab', 'yac', 'yad', 'yae', 'yaf', 'yag', 'yah', 'yai', 'yaj', 'yak', 'yal', 'yam', 'yan', 'yao', 'yap', 'yaq', 'yar', 'yas', 'yat', 'yau', 'yav', 'yaw', 'yax', 'yay', 'yaz', 'yba', 'ybb', 'ybe', 'ybh', 'ybi', 'ybj', 'ybk', 'ybl', 'ybm', 'ybn', 'ybo', 'ybx', 'yby', 'ych', 'ycl', 'ycn', 'ycp', 'yda', 'ydd', 'yde', 'ydg', 'ydk', 'yea', 'yec', 'yee', 'yei', 'yej', 'yel', 'yer', 'yes', 'yet', 'yeu', 'yev', 'yey', 'yga', 'ygi', 'ygl', 'ygm', 'ygp', 'ygr', 'ygs', 'ygu', 'ygw', 'yha', 'yhd', 'yhl', 'yhs', 'yi', 'yia', 'yif', 'yig', 'yih', 'yii', 'yij', 'yik', 'yil', 'yim', 'yin', 'yip', 'yiq', 'yir', 'yis', 'yit', 'yiu', 'yiv', 'yix', 'yiz', 'yka', 'ykg', 'yki', 'ykk', 'ykl', 'ykm', 'ykn', 'yko', 'ykr', 'ykt', 'yku', 'yky', 'yla', 'ylb', 'yle', 'ylg', 'yli', 'yll', 'ylm', 'yln', 'ylo', 'ylr', 'ylu', 'yly', 'ymb', 'ymc', 'ymd', 'yme', 'ymg', 'ymh', 'ymi', 'ymk', 'yml', 'ymm', 'ymn', 'ymo', 'ymp', 'ymq', 'ymr', 'yms', 'ymx', 'ymz', 'yna', 'ynd', 'yne', 'yng', 'ynk', 'ynl', 'ynn', 'yno', 'ynq', 'yns', 'ynu', 'yo', 'yob', 'yog', 'yoi', 'yok', 'yol', 'yom', 'yon', 'yot', 'yox', 'yoy', 'ypa', 'ypb', 'ypg', 'yph', 'ypk', 'ypm', 'ypn', 'ypo', 'ypp', 'ypz', 'yra', 'yrb', 'yre', 'yrk', 'yrl', 'yrm', 'yrn', 'yro', 'yrs', 'yrw', 'yry', 'ysc', 'ysd', 'ysg', 'ysl', 'ysn', 'yso', 'ysp', 'ysr', 'yss', 'ysy', 'yta', 'ytl', 'ytp', 'ytw', 'yty', 'yua', 'yub', 'yuc', 'yud', 'yue', 'yuf', 'yug', 'yui', 'yuj', 'yuk', 'yul', 'yum', 'yun', 'yup', 'yuq', 'yur', 'yut', 'yuw', 'yux', 'yuy', 'yuz', 'yva', 'yvt', 'ywa', 'ywg', 'ywl', 'ywn', 'ywq', 'ywr', 'ywt', 'ywu', 'yww', 'yxa', 'yxg', 'yxl', 'yxm', 'yxu', 'yxy', 'yyr', 'yyu', 'yyz', 'yzg', 'yzk', 'za', 'zaa', 'zab', 'zac', 'zad', 'zae', 'zaf', 'zag', 'zah', 'zai', 'zaj', 'zak', 'zal', 'zam', 'zao', 'zap', 'zaq', 'zar', 'zas', 'zat', 'zau', 'zav', 'zaw', 'zax', 'zay', 'zaz', 'zbc', 'zbe', 'zbl', 'zbt', 'zbw', 'zca', 'zch', 'zdj', 'zea', 'zeg', 'zeh', 'zen', 'zga', 'zgb', 'zgh', 'zgm', 'zgn', 'zgr', 'zh', 'zhb', 'zhd', 'zhi', 'zhn', 'zhw', 'zia', 'zib', 'zik', 'zil', 'zim', 'zin', 'zir', 'ziw', 'ziz', 'zka', 'zkb', 'zkd', 'zkg', 'zkh', 'zkk', 'zkn', 'zko', 'zkp', 'zkr', 'zkt', 'zku', 'zkv', 'zkz', 'zlj', 'zlm', 'zln', 'zlq', 'zma', 'zmb', 'zmc', 'zmd', 'zme', 'zmf', 'zmg', 'zmh', 'zmi', 'zmj', 'zmk', 'zml', 'zmm', 'zmn', 'zmo', 'zmp', 'zmq', 'zmr', 'zms', 'zmt', 'zmu', 'zmv', 'zmw', 'zmx', 'zmy', 'zmz', 'zna', 'znd', 'zne', 'zng', 'znk', 'zns', 'zoc', 'zoh', 'zom', 'zoo', 'zoq', 'zor', 'zos', 'zpa', 'zpb', 'zpc', 'zpd', 'zpe', 'zpf', 'zpg', 'zph', 'zpi', 'zpj', 'zpk', 'zpl', 'zpm', 'zpn', 'zpo', 'zpp', 'zpq', 'zpr', 'zps', 'zpt', 'zpu', 'zpv', 'zpw', 'zpx', 'zpy', 'zpz', 'zqe', 'zra', 'zrg', 'zrn', 'zro', 'zrp', 'zrs', 'zsa', 'zsk', 'zsl', 'zsm', 'zsr', 'zsu', 'zte', 'ztg', 'ztl', 'ztm', 'ztn', 'ztp', 'ztq', 'zts', 'ztt', 'ztu', 'ztx', 'zty', 'zu', 'zua', 'zuh', 'zum', 'zun', 'zuy', 'zwa', 'zxx', 'zyb', 'zyg', 'zyj', 'zyn', 'zyp', 'zza', 'zzj'] ) COUNTRIES = set( ['AD', 'AE', 'AF', 'AG', 'AI', 'AL', 'AM', 'AO', 'AQ', 'AR', 'AS', 'AT', 'AU', 'AW', 'AX', 'AZ', 'BA', 'BB', 'BD', 'BE', 'BF', 'BG', 'BH', 'BI', 'BJ', 'BL', 'BM', 'BN', 'BO', 'BQ', 'BR', 'BS', 'BT', 'BV', 'BW', 'BY', 'BZ', 'CA', 'CC', 'CD', 'CF', 'CG', 'CH', 'CI', 'CK', 'CL', 'CM', 'CN', 'CO', 'CR', 'CU', 'CV', 'CW', 'CX', 'CY', 'CZ', 'DE', 'DJ', 'DK', 'DM', 'DO', 'DZ', 'EC', 'EE', 'EG', 'EH', 'ER', 'ES', 'ET', 'FI', 'FJ', 'FK', 'FM', 'FO', 'FR', 'GA', 'GB', 'GD', 'GE', 'GF', 'GG', 'GH', 'GI', 'GL', 'GM', 'GN', 'GP', 'GQ', 'GR', 'GS', 'GT', 'GU', 'GW', 'GY', 'HK', 'HM', 'HN', 'HR', 'HT', 'HU', 'ID', 'IE', 'IL', 'IM', 'IN', 'IO', 'IQ', 'IR', 'IS', 'IT', 'JE', 'JM', 'JO', 'JP', 'KE', 'KG', 'KH', 'KI', 'KM', 'KN', 'KP', 'KR', 'KW', 'KY', 'KZ', 'LA', 'LB', 'LC', 'LI', 'LK', 'LR', 'LS', 'LT', 'LU', 'LV', 'LY', 'MA', 'MC', 'MD', 'ME', 'MF', 'MG', 'MH', 'MK', 'ML', 'MM', 'MN', 'MO', 'MP', 'MQ', 'MR', 'MS', 'MT', 'MU', 'MV', 'MW', 'MX', 'MY', 'MZ', 'NA', 'NC', 'NE', 'NF', 'NG', 'NI', 'NL', 'NO', 'NP', 'NR', 'NU', 'NZ', 'OM', 'PA', 'PE', 'PF', 'PG', 'PH', 'PK', 'PL', 'PM', 'PN', 'PR', 'PS', 'PT', 'PW', 'PY', 'QA', 'RE', 'RO', 'RS', 'RU', 'RW', 'SA', 'SB', 'SC', 'SD', 'SE', 'SG', 'SH', 'SI', 'SJ', 'SK', 'SL', 'SM', 'SN', 'SO', 'SR', 'SS', 'ST', 'SV', 'SX', 'SY', 'SZ', 'TC', 'TD', 'TF', 'TG', 'TH', 'TJ', 'TK', 'TL', 'TM', 'TN', 'TO', 'TR', 'TT', 'TV', 'TW', 'TZ', 'UA', 'UG', 'UM', 'US', 'UY', 'UZ', 'VA', 'VC', 'VE', 'VG', 'VI', 'VN', 'VU', 'WF', 'WS', 'YE', 'YT', 'ZA', 'ZM', 'ZW'] ) rpmlint-2.2.0+ds1/rpmlint/arparser.py000066400000000000000000000012361415540642600175520ustar00rootroot00000000000000import subprocess from rpmlint.helpers import ENGLISH_ENVIROMENT class ArParser: """ Class contains all information obtained by ar command. """ def __init__(self, pkgfile_path): self.pkgfile_path = pkgfile_path self.objects = [] self.parsing_failed_reason = None self.parse() def parse(self): r = subprocess.run(['ar', 't', self.pkgfile_path], encoding='utf8', stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=ENGLISH_ENVIROMENT) if r.returncode != 0: self.parsing_failed_reason = r.stderr return self.objects = r.stdout.splitlines() rpmlint-2.2.0+ds1/rpmlint/checks/000077500000000000000000000000001415540642600166175ustar00rootroot00000000000000rpmlint-2.2.0+ds1/rpmlint/checks/AbstractCheck.py000066400000000000000000000040411415540642600216710ustar00rootroot00000000000000import concurrent.futures import re class AbstractCheck(object): def __init__(self, config, output): # Note: do not add any capturing parentheses here self.macro_regex = re.compile(r'%+[{(]?[a-zA-Z_]\w{2,}[)}]?') self.config = config self.output = output # by default do not track checked files self.checked_files = None def check(self, pkg): if pkg.is_source: return self.check_source(pkg) return self.check_binary(pkg) def check_source(self, pkg): return def check_binary(self, pkg): return def check_spec(self, pkg): return def after_checks(self): return class AbstractFilesCheck(AbstractCheck): def __init__(self, config, output, file_regexp): self.__files_re = re.compile(file_regexp) self.use_threads = False super().__init__(config, output) def check_binary(self, pkg): if self.checked_files is None: self.checked_files = 0 filenames = [x for x in pkg.files if x not in pkg.ghost_files and self.__files_re.match(x)] if self.use_threads: # NOTE: the speed benefit of the ThreadPoolExecutor is limited due to # Global Interpreter Lock (GIL). with concurrent.futures.ThreadPoolExecutor() as executor: futures = [] for filename in filenames: futures.append(executor.submit(self.check_file, pkg, filename)) concurrent.futures.wait(futures) for future in futures: err = future.exception() if err: raise err else: for filename in filenames: self.check_file(pkg, filename) self.checked_files += len(filenames) def check_file(self, pkg, filename): """Virtual method called for each file that match the regexp passed to the constructor. """ raise NotImplementedError('check must be implemented in subclass') rpmlint-2.2.0+ds1/rpmlint/checks/AlternativesCheck.py000066400000000000000000000305401415540642600225720ustar00rootroot00000000000000from os.path import basename from pathlib import Path import re import stat import rpm from rpmlint.checks.AbstractCheck import AbstractCheck from rpmlint.helpers import byte_to_string class AlternativesCheck(AbstractCheck): """ Check for compliance with update-alternatives usage guidelines: http://en.opensuse.org/openSUSE:Packaging_Multiple_Version_guidelines https://docs.fedoraproject.org/en-US/packaging-guidelines/Alternatives/ In short the rules are: /etc/alternative/basename must be in %files and must be %ghost file The alternative master must be a symlink to /etc and in filelist. I.e.: /usr/bin/basename -> /etc/alternative/basename In %post the update-alternatives with install must be called In %postun the update-alternatives with remove must be called Requires(post) and Requires(postun) must depend on update-alternatives """ # Regex to match anything that can be in requires for update-alternatives re_requirement = re.compile(r'^(/usr/sbin/|%{?_sbindir}?/)?update-alternatives$') re_install = re.compile(r'--install\s+(?P\S+)\s+(?P\S+)\s+(\S+)\s+(\S+)') re_slave = re.compile(r'--slave\s+(?P\S+)\s+(\S+)\s+(\S+)') command = 'update-alternatives' alts_requirement = 'alts' def __init__(self, config, output): super().__init__(config, output) # Containers for scriptlets as they will be used on multiple places self.post = None self.postun = None self.install_binaries = {} self.slave_binaries = [] def check(self, pkg): if pkg.is_source: return if self._check_libalternatives_presence(pkg): self.output.add_info('I', pkg, 'package supports libalternatives') self._check_libalternatives_requirements(pkg) self._check_libalternatives_filelist(pkg) # populate scriptlets self.post = byte_to_string(pkg.header[rpm.RPMTAG_POSTIN]) self.postun = byte_to_string(pkg.header[rpm.RPMTAG_POSTUN]) if not self._check_ua_presence(pkg): return self.output.add_info('I', pkg, 'package supports update-alternatives') self._check_requirements(pkg) self._check_post_phase(pkg, self.post) self._check_postun_phase(pkg, self.postun) self._check_filelist(pkg) def _find_u_a_binarires(self, line): """ Find all binaries that have install or slave that are needed to be validated. update-alternatives --install link name path priority [--slave link name path]+ """ match = self.re_install.search(line) if match: self.install_binaries[match.group('link')] = match.group('name') # --slave can be repeated multiple times matches = self.re_slave.finditer(line) for match in matches: self.slave_binaries.append(match.group('link')) def _check_post_phase(self, pkg, script): """ Validate that post phase contains the update-alternatives --install call Collect all binaries that are to be validated for the usage """ script = self._normalize_script(script) # If there is no u-a call then give up right away if not script: self.output.add_info('E', pkg, 'update-alternatives-post-call-missing') return # collect all the known binaries for line in script: self._find_u_a_binarires(line) # if there is u-a call, but no --install command it is still an issue if not self.install_binaries: self.output.add_info('E', pkg, 'update-alternatives-post-call-missing') def _check_postun_phase(self, pkg, script): """ Validate that post phase contains the update-alternatives --remove call Make sure there is --remove line for all installed binaries update-alternatives --remove name path """ script = self._normalize_script(script) # If there is no u-a call then give up right away if not script: self.output.add_info('E', pkg, 'update-alternatives-postun-call-missing') return # validate each binary actually is properly removed binaries = list(self.install_binaries.values()) # we remove from the binaries list in the loop, copy it for binary in binaries.copy(): re_remove = re.compile(r'--remove\s+{}\b'.format(re.escape(binary))) for line in script: if re_remove.search(line) and binary in binaries: binaries.remove(binary) for binary in binaries: self.output.add_info('E', pkg, 'update-alternatives-postun-call-missing', binary) def _check_filelist(self, pkg): """ Validate all filelists for required content to make u-a work: * For each install/slave binary I need /etc/alternatives/X + This file must be in filelist marked as ghost * The install/slave binary must be present in filelist + The item must be a a link to /etc/alternatives """ files = pkg.files ghost_files = pkg.ghost_files for binary in self.slave_binaries + list(self.install_binaries.keys()): etc_alt_file = '/etc/alternatives/%s' % basename(binary) if etc_alt_file not in files: # The alternative is missing completely self.output.add_info('E', pkg, 'alternative-link-missing', etc_alt_file) elif etc_alt_file not in ghost_files: # The alternative is present, but not as ghost self.output.add_info('E', pkg, 'alternative-link-not-ghost', etc_alt_file) # generic-name should be a symlink to /etc/alternatives/$(basename) if binary not in files: self.output.add_info('E', pkg, 'alternative-generic-name-missing', binary) elif not stat.S_ISLNK(files[binary].mode): self.output.add_info('E', pkg, 'alternative-generic-name-not-symlink', binary) def _check_ua_presence(self, pkg): """ Check if there is update-alternatives scriptlet present and if we should do validation """ # first check just if we have anything in /etc/alternatives for path in pkg.files: if path.startswith('/etc/alternatives'): return True # then check the scriptlets if they run update-alternatives if self._check_scriptlet_for_alternatives(self.post): return True if self._check_scriptlet_for_alternatives(self.postun): return True return False def _check_libalternatives_presence(self, pkg): """ Check if there is libalternatives scriptlet present """ # first check just if we have anything in /usr/share/libalternatives/ for path in pkg.files: if path.startswith('/usr/share/libalternatives/'): return True # then check if package with the name "alts" is required for req in pkg.requires + pkg.prereq: if req[0] == self.alts_requirement: return True return False def _check_scriptlet_for_alternatives(self, scriptlet): """ Check if scriptlet actually contains the update-alternatives call """ if scriptlet is not None and self.command in scriptlet: return True return False def _normalize_script(self, script): """ Remove "backslash+newline" to keep all commands as oneliners. Remove single and double quotes everywhere. Keep only the line that contains the update-alternatives call. Return the list of lines that contain update-alternatives calls """ # with old rpm we get wrong type script = byte_to_string(script) if script is None: return None script = script.replace('\\\n', '') script = script.replace('"', '') script = script.replace("'", '') script = script.strip() return [i for i in script.splitlines() if self.command in i] def _check_requirements(self, pkg): """ Check that Requires(post/postun) contain the update-alternatives dependency """ for require in pkg.prereq: if self.re_requirement.match(require[0]): return self.output.add_info('E', pkg, 'update-alternatives-requirement-missing') def _check_libalternatives_requirements(self, pkg): """ Check the requirement of package "alts" """ for req in pkg.requires + pkg.prereq: if req[0] == self.alts_requirement: return self.output.add_info('E', pkg, 'alts-requirement-missed') def _check_libalternatives_filelist(self, pkg): """ Checking if all links to "alts" have corresponding entries in /usr/share/libalternatives. """ for f, pkgfile in pkg.files.items(): if pkgfile.linkto == Path(self.alts_requirement).name: dir_name = '/usr/share/libalternatives/' + Path(f).name if dir_name not in pkg.files: self.output.add_info('E', pkg, 'libalternatives-directory-not-exists', dir_name) else: r = re.compile('^' + dir_name + '/.*.conf$') if not list(filter(r.match, pkg.files)): self.output.add_info('E', pkg, 'empty-libalternatives-directory', dir_name) """ Checking content of all /usr/share/libalternatives/*/*.conf files """ for f, pkgfile in pkg.files.items(): if re.search('^/usr/share/libalternatives/.*conf$', f): filename = Path(pkg.dirname + f) if not filename.exists(): if pkgfile.is_ghost: self.output.add_info('I', pkg, 'libalternatives-conf-not-found', f) else: self.output.add_info('E', pkg, 'libalternatives-conf-not-found', f) continue bin_found = False man_found = False with open(filename) as read_obj: # Read all lines in the file one by one. E.g: # # binary=/usr/bin/jupyter-3.8 # man=jupyter-3.8.1 # group=jupyter, jupyter-migrate, jupyter-troubleshoot # for line_nr, line in enumerate(read_obj): line_array = [x.strip() for x in line.split('=')] line_nr_str = f'Line: {line_nr}' if len(line_array) != 2: # empty values are valid self.output.add_info('E', pkg, 'wrong-entry-format', f, line_nr_str) key, value = line_array if key == 'binary': if bin_found: self.output.add_info('E', pkg, 'multiple-entries', f, line_nr_str) continue for path in pkg.files: if 'bin/' in path and path.endswith(value): bin_found = True if not bin_found: self.output.add_info('W', pkg, 'binary-entry-value-not-found', f, line_nr_str) elif key == 'man': if man_found: self.output.add_info('E', pkg, 'double-entries', f, line_nr_str) continue mans = value.split(',') for man in mans: man_found = False for path in pkg.files: if path.startswith('/usr/share/man/') and man.strip() in path: man_found = True if not man_found: self.output.add_info('W', pkg, 'man-entry-value-not-found', f, line_nr_str) elif not key == 'group' and not key == 'options': self.output.add_info('W', pkg, 'wrong-tag-found', f, line_nr_str) if not bin_found: self.output.add_info('W', pkg, 'wrong-or-missed-binary-entry', f) rpmlint-2.2.0+ds1/rpmlint/checks/AppDataCheck.py000066400000000000000000000023401415540642600214400ustar00rootroot00000000000000import subprocess from xml.etree import ElementTree from rpmlint.checks.AbstractCheck import AbstractFilesCheck from rpmlint.helpers import ENGLISH_ENVIROMENT class AppDataCheck(AbstractFilesCheck): """ check appdata files for format violations https://www.freedesktop.org/software/appstream/docs/ """ # default command, split here so we can mock it later cmd = 'appstream-util validate-relax --nonet ' def __init__(self, config, output): super().__init__(config, output, r'/usr/share/appdata/.*\.(appdata|metainfo).xml$') def check_file(self, pkg, filename): root = pkg.dirName() f = root + filename cmd = self.cmd + f validation_failed = False try: r = subprocess.run(cmd.split(), env=ENGLISH_ENVIROMENT) if r.returncode != 0: validation_failed = True except FileNotFoundError: # appstream-util is not installed # validate the xml format only try: ElementTree.parse(f) except ElementTree.ParseError: validation_failed = True if validation_failed: self.output.add_info('E', pkg, 'invalid-appdata-file', filename) rpmlint-2.2.0+ds1/rpmlint/checks/BashismsCheck.py000066400000000000000000000044231415540642600217030ustar00rootroot00000000000000import stat import subprocess from rpmlint.checks.AbstractCheck import AbstractFilesCheck from rpmlint.helpers import ENGLISH_ENVIROMENT class BashismsCheck(AbstractFilesCheck): def __init__(self, config, output): super().__init__(config, output, r'.*') self.use_threads = True self._detect_early_fail_option() def _detect_early_fail_option(self): self.has_early_fail_option = False output = subprocess.check_output(['checkbashisms', '--help'], shell=True, encoding='utf8') # FIXME: remove in the future self.use_early_fail = '[-e]' in output def check_file(self, pkg, filename): root = pkg.dirName() pkgfile = pkg.files[filename] filepath = root + filename # We only care about the real files that state they are shell scripts if not (stat.S_ISREG(pkgfile.mode) and pkgfile.magic.startswith('POSIX shell script')): return self.check_bashisms(pkg, filepath, filename) def check_bashisms(self, pkg, filepath, filename): """ Run dash and then checkbashism on file We need to see if it is valid syntax of bash and if there are no potential bash issues. """ try: r = subprocess.run(['dash', '-n', filepath], stderr=subprocess.DEVNULL, env=ENGLISH_ENVIROMENT) if r.returncode == 2: self.output.add_info('W', pkg, 'bin-sh-syntax-error', filename) elif r.returncode == 127: raise FileNotFoundError(filename) except UnicodeDecodeError: pass try: cmd = ['checkbashisms', filepath] # --early-fail option can rapidly speed up the check if self.use_early_fail: cmd.append('-e') r = subprocess.run(cmd, stderr=subprocess.DEVNULL, env=ENGLISH_ENVIROMENT) if r.returncode == 1: self.output.add_info('W', pkg, 'potential-bashisms', filename) elif r.returncode == 2: raise FileNotFoundError(filename) except UnicodeDecodeError: pass rpmlint-2.2.0+ds1/rpmlint/checks/BinariesCheck.py000066400000000000000000000745651415540642600217040ustar00rootroot00000000000000import concurrent.futures from pathlib import Path import re import stat import rpm from rpmlint.arparser import ArParser from rpmlint.checks.AbstractCheck import AbstractCheck from rpmlint.lddparser import LddParser from rpmlint.objdumpparser import ObjdumpParser from rpmlint.pkg import FakePkg, InstalledPkg from rpmlint.readelfparser import ReadelfParser from rpmlint.stringsparser import StringsParser KERNEL_MODULES_PATHS = ('/lib/modules/', '/usr/lib/modules/') GLIBC_EMPTY_ARCHIVES = ('libanl', 'libdl', 'libpthread', 'librt', 'libutil') class BinariesCheck(AbstractCheck): """ Checks for binary files in the package. """ srcname_regex = re.compile(r'(.*?)-[0-9]') validso_regex = re.compile(r'(\.so\.\d+(\.\d+)*|\d\.so)$') soversion_regex = re.compile(r'.*?(-(?P[0-9][.0-9]*))?\.so(\.(?P[0-9][.0-9]*))?') usr_lib_regex = re.compile(r'^/usr/lib(64)?/') ldso_soname_regex = re.compile(r'^ld(-linux(-(ia|x86_)64))?\.so') numeric_dir_regex = re.compile(r'/usr(?:/share)/man/man./(.*)\.[0-9](?:\.gz|\.bz2)') versioned_dir_regex = re.compile(r'[^.][0-9]') so_regex = re.compile(r'/lib(64)?/[^/]+\.so(\.[0-9]+)*$') bin_regex = re.compile(r'^(/usr(/X11R6)?)?/s?bin/') la_file_regex = re.compile(r'\.la$') invalid_dir_ref_regex = re.compile(r'/(home|tmp)(\W|$)') usr_arch_share_regex = re.compile(r'/share/.*/(?:x86|i.86|x86_64|ppc|ppc64|s390|s390x|ia64|m68k|arm|aarch64|mips|riscv)') lto_text_like_sections = {'.preinit_array', '.init_array', '.fini_array'} # The following sections are part of the RX ABI and do correspond to .text, .data and .bss lto_text_like_sections |= {'P', 'D_1', 'B_1'} # The list is taken from glibc: sysdeps/${arch}/stackinfo.h default_executable_stack_archs = re.compile(r'alpha|arm.*|hppa|i.86|m68k|microblaze|mips|ppc64|ppc64le|s390|s390x|sh|sparc|x86_64') def __init__(self, config, output): super().__init__(config, output) self.checked_files = 0 self.system_lib_paths = config.configuration['SystemLibPaths'] self.pie_exec_regex_list = [] for regex in config.configuration['PieExecutables']: self.pie_exec_regex_list.append(re.compile(regex)) self.usr_lib_exception_regex = re.compile(config.configuration['UsrLibBinaryException']) self.setgid_call_regex = self.create_regexp_call(r'set(?:res|e)?gid') self.setuid_call_regex = self.create_regexp_call(r'set(?:res|e)?uid') self.setgroups_call_regex = self.create_regexp_call(r'(?:ini|se)tgroups') self.mktemp_call_regex = self.create_regexp_call('mktemp') self.gethostbyname_call_regex = self.create_regexp_call(r'(gethostbyname|gethostbyname2|gethostbyaddr|gethostbyname_r|gethostbyname2_r|gethostbyaddr_r)') # register all check functions self.check_functions = [self._check_lto_section, self._check_no_text_in_archive, self._check_missing_symtab_in_archive, self._check_missing_debug_info_in_archive, self._check_executable_stack, self._check_shared_library, self._check_dependency, self._check_library_dependency_location, self._check_security_functions, self._check_rpath, self._check_library_dependency, self._check_forbidden_functions, self._check_executable_shlib, self._check_optflags] @staticmethod def create_nonlibc_regexp_call(call): r = r'(%s)\s?.*$' % call return re.compile(r) @staticmethod def create_regexp_call(call): r = r'(%s(?:@GLIBC\S+)?)(?:\s|$)' % call return re.compile(r) def _check_libtool_wrapper(self, pkg, fname, pkgfile): """ Print an error if the fname file contains a libtool wrapper shell script. """ if 'shell script' in pkgfile.magic: file_start = None try: with open(pkgfile.path, 'rb') as inputf: file_start = inputf.read(2048) except IOError: pass if (file_start and b'This wrapper script should never ' b'be moved out of the build directory' in file_start): self.output.add_info('E', pkg, 'libtool-wrapper-in-package', fname) def _check_invalid_la_file(self, pkg, fname): """ Check if the fname is an .la file and contains a reference to the invalid directories ('/tmp' or '/home'). If so then print a corresponding error with the matching line numbers. """ if self.la_file_regex.search(fname): lines = pkg.grep(self.invalid_dir_ref_regex, fname) if lines: self.output.add_info('E', pkg, 'invalid-la-file', fname, '(line %s)' % ', '.join(lines)) def _check_binary_in_noarch(self, pkg, bin_name): """ Print an error if the binary file bin_name is in the noarch package. """ if pkg.arch == 'noarch': self.output.add_info('E', pkg, 'arch-independent-package-contains-binary-or-object', bin_name) def _check_binary_in_usr_share(self, pkg, bin_name): """ Print an error if binary file bin_name is installed in /usr/share. We suppose that the package is arch dependent. """ if bin_name.startswith('/usr/share/') and \ not self.usr_arch_share_regex.search(bin_name): self.output.add_info('E', pkg, 'arch-dependent-file-in-usr-share', bin_name) def _check_binary_in_etc(self, pkg, bin_name): """ Print an error if binary file bin_name is installed in /etc directory. We suppose that the package is arch dependent. """ if bin_name.startswith('/etc/'): self.output.add_info('E', pkg, 'binary-in-etc', bin_name) def _check_unstripped_binary(self, bin_name, pkg, pkgfile): """ Print a warning if the bin_name binary has unstripped debug symbols. We suppose that the package is arch dependent and bin_name is not ocaml native, lua bytecode, .o or .static. """ if 'not stripped' in pkgfile.magic: self.output.add_info('W', pkg, 'unstripped-binary-or-object', bin_name) def _check_non_pie(self, pkg, bin_name): """ Check if the bin_name binary is built with PIE. Print an error message if it's not while PIE is forced in configuration. Print a warning if it's not forced. We suppose that the package is arch dependent and bin_name is binary executable. """ if not self.is_shobj and not self.is_pie_exec: if any(regex.fullmatch(bin_name) for regex in self.pie_exec_regex_list): self.output.add_info('E', pkg, 'non-position-independent-executable', bin_name) else: self.output.add_info('W', pkg, 'position-independent-executable-suggested', bin_name) def _check_exec_in_library(self, pkg, has_lib, exec_files): """ Check if the library package has an executable file installed. Print an error for every such file. """ if has_lib: for f in exec_files: self.output.add_info('E', pkg, 'executable-in-library-package', f) def _check_non_versioned(self, pkg, has_lib, exec_files): """ Check if the library package contains library files in non-versioned directories. Print an error for every such file. """ if has_lib: for f in pkg.files: res = self.numeric_dir_regex.search(f) fn = res and res.group(1) or f if f not in exec_files and not self.so_regex.search(f) and \ not self.versioned_dir_regex.search(fn): self.output.add_info('E', pkg, 'non-versioned-file-in-library-package', f) def _check_no_binary(self, pkg, has_binary, multi_pkg, has_file_in_lib64): """ Check if the arch dependent package contains any binaries. Print an error if there is no binary and it's not noarch. """ if not has_binary and not multi_pkg and not has_file_in_lib64 and \ pkg.arch != 'noarch': self.output.add_info('E', pkg, 'no-binary') def _check_noarch_with_lib64(self, pkg, has_file_in_lib64): """ Print an error if we have a noarch package that contains a file in /usr/lib64. """ if pkg.arch == 'noarch' and has_file_in_lib64: self.output.add_info('E', pkg, 'noarch-with-lib64') def _check_only_non_binary_in_usrlib(self, pkg, has_usr_lib_file, has_binary_in_usr_lib): """ Check and print a warning if we have _only_ non-binary files in the '/usr/lib'. Note: non-binaries allowed via UsrLibBinaryException config option are considered binaries. """ if has_usr_lib_file and not has_binary_in_usr_lib: self.output.add_info('W', pkg, 'only-non-binary-in-usr-lib') def _check_no_text_in_archive(self, pkg, pkgfile_path, path): """ For an archive, test if any .text sections is non-empty. """ if self.is_archive: for comment in self.readelf_parser.comment_section_info.comments: if comment.startswith('GHC '): return # Starting with glibc 2.34, some static libraries were moved to libc # and there are empty archives for backward compatibility. Skip these # libraries. stem = Path(path).stem if stem in GLIBC_EMPTY_ARCHIVES or (stem.endswith('_p') and stem[:-2] in GLIBC_EMPTY_ARCHIVES): return for elf_file in self.readelf_parser.section_info.elf_files: for section in elf_file: sn = section.name if ((sn in self.lto_text_like_sections or sn == '.fini_array' or sn.startswith('.text') or sn.startswith('.data')) and section.size > 0): return self.output.add_info('E', pkg, 'lto-no-text-in-archive', path) return def _check_missing_symtab_in_archive(self, pkg, pkgfile_path, path): """ FIXME Add test coverage. """ if self.is_archive: for elf_file in self.readelf_parser.section_info.elf_files: for section in elf_file: if section.name == '.symtab': return self.output.add_info('E', pkg, 'static-library-without-symtab', path) def _check_missing_debug_info_in_archive(self, pkg, pkgfile_path, path): if self.is_archive: for elf_file in self.readelf_parser.section_info.elf_files: for section in elf_file: if section.name.startswith('.debug_'): return self.output.add_info('E', pkg, 'static-library-without-debuginfo', path) # Check for LTO sections def _check_lto_section(self, pkg, pkgfile_path, path): for elf_file in self.readelf_parser.section_info.elf_files: for section in elf_file: if '.gnu.lto_.' in section.name: self.output.add_info('E', pkg, 'lto-bytecode', path) return def _check_executable_stack(self, pkg, pkgfile_path, path): """ Check if the stack is declared as executable which is usually an error. FIXME Add test coverage. """ # Skip architectures that have non-executable stack by default if pkg.arch and not self.default_executable_stack_archs.fullmatch(pkg.arch): return # Do not check kernel modules and archives if not self.is_archive and not any(path.startswith(p) for p in KERNEL_MODULES_PATHS): stack_headers = [h for h in self.readelf_parser.program_header_info.headers if h.name == 'GNU_STACK'] if not stack_headers: self.output.add_info('E', pkg, 'missing-PT_GNU_STACK-section', path) elif 'E' in stack_headers[0].flags: self.output.add_info('E', pkg, 'executable-stack', path) def _check_soname_symlink(self, pkg, shlib, soname): """ Check that we have a symlink with the soname in the package and it points to the checked shared library. Print an error if the symlink is invalid or missing. """ path = Path(shlib) symlink = path.parent / soname try: link = pkg.files[str(symlink)].linkto if link not in (shlib, path.name, ''): self.output.add_info('E', pkg, 'invalid-ldconfig-symlink', shlib, link) except KeyError: # if we do not have a symlink, report an issue if path.name.startswith('lib') or path.name.startswith('ld-'): self.output.add_info('E', pkg, 'no-ldconfig-symlink', shlib) def _check_shared_library(self, pkg, pkgfile_path, path): """ Various checks for the shared library. 1) Print 'no-soname' warning it the library has no soname present. 2) Print 'invalid-soname' error if the soname is not valid. 3) Print 'shlib-policy-name-error' error when the library major version is not present in the package name. 4) Print 'shlib-with-non-pic-code' error if the library contains object code that was compiled without -fPIC. """ if not self.readelf_parser.is_shlib: return soname = self.readelf_parser.dynamic_section_info.soname if not soname: self.output.add_info('W', pkg, 'no-soname', path) else: if not self.validso_regex.search(soname): self.output.add_info('E', pkg, 'invalid-soname', path, soname) else: self._check_soname_symlink(pkg, path, soname) # check if the major version of the library is in the package # name (check only for lib* packages) if pkg.name.startswith('lib'): # SLPP is defined here: https://en.opensuse.org/openSUSE:Shared_library_packaging_policy#Package_naming # Example: # SONAME = libgame2-1.9.so.10.0.0 # expected package name: libgame2-1_9-10_0_0 res = self.soversion_regex.search(soname) if res: parts = [x.replace('.', '_') for x in (res.group('pkgversion'), res.group('soversion')) if x] soversion = '-'.join(parts) pkgname = pkg.name if '.' in pkgname: pkgname = pkgname[:pkgname.rindex('.')] if soversion and not pkgname.endswith(soversion): self.output.add_info('E', pkg, 'shlib-policy-name-error', f'SONAME: {soname}, expected package suffix: {soversion}') # check if the object code in the library is compiled with PIC if self.readelf_parser.dynamic_section_info['TEXTREL']: self.output.add_info('E', pkg, 'shlib-with-non-pic-code', path) def _check_dependency(self, pkg, pkgfile_path, path): """ FIXME Add test coverage. """ # Undefined symbol and unused direct dependency checks make sense only # for installed packages. # skip debuginfo: https://bugzilla.redhat.com/190599 # # following issues are errors for shared libs and warnings for executables if not self.is_dynamically_linked: return if not self.is_archive and not self.readelf_parser.is_debug: info_type = 'E' if self.readelf_parser.is_shlib else 'W' for symbol in self.ldd_parser.undefined_symbols: self.output.add_info(info_type, pkg, 'undefined-non-weak-symbol', path, symbol) for dependency in self.ldd_parser.unused_dependencies: self.output.add_info(info_type, pkg, 'unused-direct-shlib-dependency', path, dependency) def _check_library_dependency_location(self, pkg, pkgfile_path, path): """ FIXME Add test coverage. """ if not self.is_dynamically_linked: return if not self.is_archive: for dependency in self.ldd_parser.dependencies: if dependency.startswith('/opt/'): self.output.add_info('E', pkg, 'linked-against-opt-library', path, dependency) break nonusr = ('/bin', '/lib', '/sbin') if path.startswith(nonusr): for dependency in self.ldd_parser.dependencies: if dependency.startswith('/usr/'): self.output.add_info('W', pkg, 'linked-against-usr-library', path, dependency) break def _check_security_functions(self, pkg, pkgfile_path, path): setgid = any(self.readelf_parser.symbol_table_info.get_functions_for_regex(self.setgid_call_regex)) setuid = any(self.readelf_parser.symbol_table_info.get_functions_for_regex(self.setuid_call_regex)) setgroups = any(self.readelf_parser.symbol_table_info.get_functions_for_regex(self.setgroups_call_regex)) mktemp = any(self.readelf_parser.symbol_table_info.get_functions_for_regex(self.mktemp_call_regex)) gethostbyname = any(self.readelf_parser.symbol_table_info.get_functions_for_regex(self.gethostbyname_call_regex)) if setgid and setuid and not setgroups: self.output.add_info('E', pkg, 'missing-call-to-setgroups-before-setuid', path) if mktemp: self.output.add_info('E', pkg, 'call-to-mktemp', path) if gethostbyname: self.output.add_info('W', pkg, 'binary-or-shlib-calls-gethostbyname', path) def _check_rpath(self, pkg, pkgfile_path, path): for runpath in self.readelf_parser.dynamic_section_info.runpath: if runpath in self.system_lib_paths or not self.usr_lib_regex.search(runpath): self.output.add_info('E', pkg, 'binary-or-shlib-defines-rpath', path, runpath) return def _check_library_dependency(self, pkg, pkgfile_path, path): if self.is_archive: return if any(path.startswith(p) for p in KERNEL_MODULES_PATHS): return dyn_section = self.readelf_parser.dynamic_section_info if not len(dyn_section.needed) and not (dyn_section.soname and self.ldso_soname_regex.search(dyn_section.soname)): if self.is_shobj: msg = 'shared-library-without-dependency-information' else: msg = 'statically-linked-binary' self.output.add_info('E', pkg, msg, path) else: # linked against libc ? if 'libc.' not in dyn_section.runpath and \ (not dyn_section.soname or ('libc.' not in dyn_section.soname and not self.ldso_soname_regex.search(dyn_section.soname))): for lib in dyn_section.needed: if 'libc.' in lib: return if self.is_shobj: msg = 'library-not-linked-against-libc' else: msg = 'program-not-linked-against-libc' self.output.add_info('W', pkg, msg, path) def _check_forbidden_functions(self, pkg, pkgfile_path, path): forbidden_functions = self.config.configuration['WarnOnFunction'] if forbidden_functions: for name, func in forbidden_functions.items(): # precompile regexps f_name = func['f_name'] func['f_regex'] = self.create_nonlibc_regexp_call(f_name) if 'good_param' in func and func['good_param']: func['waiver_regex'] = re.compile(func['good_param']) # register descriptions self.output.error_details.update({name: func['description']}) forbidden_calls = [] for r_name, func in forbidden_functions.items(): if any(self.readelf_parser.symbol_table_info.get_functions_for_regex(func['f_regex'])): forbidden_calls.append(r_name) if not forbidden_calls: return strings_parser = StringsParser(pkgfile_path) failed_reason = strings_parser.parsing_failed_reason if failed_reason: self.output.add_info('E', pkg, 'strings-failed', path, failed_reason) return forbidden_functions_filtered = [] for fn in forbidden_calls: f = forbidden_functions[fn] if 'waiver_regex' not in f: forbidden_functions_filtered.append(fn) continue waiver = any(map(lambda string: f['waiver_regex'].search(string), strings_parser.strings)) if not waiver: forbidden_functions_filtered.append(fn) for fn in forbidden_functions_filtered: self.output.add_info('W', pkg, fn, path, forbidden_functions[fn]['f_name']) def _check_executable_shlib(self, pkg, pkgfile_path, path): if not self.is_exec and self.readelf_parser.is_shlib: interp = [h for h in self.readelf_parser.program_header_info.headers if h.name == 'INTERP'] if interp: self.output.add_info('E', pkg, 'shared-library-not-executable', path) def _check_optflags(self, pkg, pkgfile_path, path): if self.is_archive: return mandatory_optflags = self.config.configuration['MandatoryOptflags'] forbidden_optflags = self.config.configuration['ForbiddenOptflags'] if not mandatory_optflags and not forbidden_optflags: return for dwarf_unit in self.objdump_parser.compile_units: tokens = dwarf_unit['producer'].split(' ') missing = [mo for mo in mandatory_optflags if mo not in tokens] forbidden = [f for f in forbidden_optflags if f in tokens] if missing: self.output.add_info('W', pkg, 'missing-mandatory-optflags', path, ' '.join(missing)) if forbidden: self.output.add_info('E', pkg, 'forbidden-optflags', path, ' '.join(forbidden)) def _is_standard_archive(self, pkg, pkgfile_path, path): # skip Klee bytecode archives if pkgfile_path.endswith('.bca'): return False # return false for e.g. Rust or Go packages that are archives # but files in the archive are not an ELF container ar_parser = ArParser(pkgfile_path) failed_reason = ar_parser.parsing_failed_reason if failed_reason: self.output.add_info('E', pkg, 'ar-failed', path, failed_reason) return False needles = ('__.PKGDEF', '_go_.o', 'lib.rmeta') return not any(needle for needle in needles if needle in ar_parser.objects) def _detect_attributes(self, magic): self.is_exec = 'executable' in magic self.is_shobj = 'shared object' in magic self.is_archive = 'current ar archive' in magic self.is_dynamically_linked = 'dynamically linked' in magic self.is_pie_exec = 'pie executable' in magic self.is_nonstandard_archive = False def run_elf_checks(self, pkg, pkgfile_path, path): if self.is_archive and not self._is_standard_archive(pkg, pkgfile_path, path): self.is_nonstandard_archive = True return self.readelf_parser = ReadelfParser(pkgfile_path, path) failed_reason = self.readelf_parser.parsing_failed_reason() if failed_reason: self.output.add_info('E', pkg, 'readelf-failed', path, failed_reason) return if not self.is_archive: if self.is_dynamically_linked: is_installed_pkg = isinstance(pkg, InstalledPkg) or isinstance(pkg, FakePkg) self.ldd_parser = LddParser(pkgfile_path, path, is_installed_pkg) failed_reason = self.ldd_parser.parsing_failed_reason if failed_reason: self.output.add_info('E', pkg, 'ldd-failed', path, failed_reason) return self.objdump_parser = ObjdumpParser(pkgfile_path, path) failed_reason = self.objdump_parser.parsing_failed_reason if failed_reason: self.output.add_info('E', pkg, 'objdump-failed', path, failed_reason) return # NOTE: the speed benefit of the ThreadPoolExecutor is limited due to # Global Interpreter Lock (GIL). with concurrent.futures.ThreadPoolExecutor() as executor: futures = [] for fn in self.check_functions: futures.append(executor.submit(fn, pkg, pkgfile_path, path)) concurrent.futures.wait(futures) for future in futures: err = future.exception() if err: raise err def check_binary(self, pkg): exec_files = [] multi_pkg = False pkg_has_lib = False pkg_has_binary = False pkg_has_binary_in_usrlib = False pkg_has_usrlib_file = False pkg_has_file_in_lib64 = False # go through the all files, run files checks and collect data that are # needed later for fname, pkgfile in pkg.files.items(): # Common tests first self._check_libtool_wrapper(pkg, fname, pkgfile) self._check_invalid_la_file(pkg, fname) # consider non-binary in /usr/lib/ that is allowed by # UsrLibBinaryException config option as a "fake" binary and # do not throw 'only-non-binary-in-usr-lib' warning then if not stat.S_ISDIR(pkgfile.mode) and self.usr_lib_regex.search(fname): pkg_has_usrlib_file = True if not pkg_has_binary_in_usrlib and \ self.usr_lib_exception_regex.search(fname): # Fake that we have binaries there to avoid # only-non-binary-in-usr-lib false positives pkg_has_binary_in_usrlib = True # find out if we have a file in /usr/lib64/ directory (needed later # for the package checks) if stat.S_ISREG(pkgfile.mode) and \ (fname.startswith('/usr/lib64') or fname.startswith('/lib64')): pkg_has_file_in_lib64 = True # skip the rest of the tests for non-binaries # binary files only from here on is_ocaml_native = 'Objective caml native' in pkgfile.magic is_lua_bytecode = 'Lua bytecode' in pkgfile.magic if not (pkgfile.magic.startswith('ELF ') or 'current ar archive' in pkgfile.magic or is_ocaml_native or is_lua_bytecode): continue self.checked_files += 1 # mark this package as a one that has binary file pkg_has_binary = True # if there is a binary in /usr/lib then mark this package # accordingly if pkg_has_usrlib_file and not pkg_has_binary_in_usrlib and \ self.usr_lib_regex.search(fname): pkg_has_binary_in_usrlib = True self._check_binary_in_noarch(pkg, fname) # skip the rest of the tests for noarch packages # arch dependent packages only from here on if pkg.arch == 'noarch': continue self._check_binary_in_usr_share(pkg, fname) self._check_binary_in_etc(pkg, fname) # skip the rest of the tests for ocaml native, Lua bytecode, # Go .go and .gox, .o and .static if is_ocaml_native or is_lua_bytecode or fname.endswith('.o') or \ fname.endswith('.static') or fname.endswith('.gox') or \ fname.endswith('.go'): continue self._check_unstripped_binary(fname, pkg, pkgfile) # Detect attributes of an ELF file self._detect_attributes(pkgfile.magic) # run ELF checks self.run_elf_checks(pkg, pkgfile.path, fname) if self.is_nonstandard_archive: continue # inspect binary file is_shlib = self.readelf_parser.is_shlib if is_shlib: pkg_has_lib = True # skip non-exec and non-SO # executables and shared objects only from here on if not self.is_exec and not self.is_shobj: continue if self.is_shobj and not self.is_exec and '.so' not in fname and \ self.bin_regex.search(fname): # pkgfile.magic does not contain 'executable' for PIEs self.is_exec = True if self.is_exec: # add to the list of the all exec files if self.bin_regex.search(fname): exec_files.append(fname) self._check_non_pie(pkg, fname) # find out if we have a multi-package srpm = pkg[rpm.RPMTAG_SOURCERPM] if srpm: srcname = self.srcname_regex.search(srpm) if srcname: multi_pkg = (pkg.name != srcname.group(1)) # run checks for the whole package # it uses data collected in the previous for-cycle self._check_exec_in_library(pkg, pkg_has_lib, exec_files) self._check_non_versioned(pkg, pkg_has_lib, exec_files) self._check_no_binary(pkg, pkg_has_binary, multi_pkg, pkg_has_file_in_lib64) self._check_noarch_with_lib64(pkg, pkg_has_file_in_lib64) self._check_only_non_binary_in_usrlib(pkg, pkg_has_usrlib_file, pkg_has_binary_in_usrlib) rpmlint-2.2.0+ds1/rpmlint/checks/BuildDateCheck.py000066400000000000000000000021021415540642600217570ustar00rootroot00000000000000import re import stat import time from rpmlint.checks.AbstractCheck import AbstractFilesCheck class BuildDateCheck(AbstractFilesCheck): """ Check that the file doesn't contain the current date or time. If so, it causes the package to rebuild when it's not needed. """ def __init__(self, config, output): super().__init__(config, output, r'.*') self.looksliketime = re.compile('(2[0-3]|[01]?[0-9]):([0-5]?[0-9]):([0-5]?[0-9])') self.istoday = re.compile(time.strftime('%b %e %Y')) def check_file(self, pkg, filename): if filename.startswith('/usr/lib/debug') or pkg.is_source or \ not stat.S_ISREG(pkg.files[filename].mode): return grep_date = pkg.grep(self.istoday, filename) if len(grep_date): grep_time = pkg.grep(self.looksliketime, filename) if len(grep_time): self.output.add_info('E', pkg, 'file-contains-date-and-time', filename) else: self.output.add_info('E', pkg, 'file-contains-current-date', filename) rpmlint-2.2.0+ds1/rpmlint/checks/BuildRootCheck.py000066400000000000000000000015431415540642600220350ustar00rootroot00000000000000import re import stat import rpm from rpmlint.checks.AbstractCheck import AbstractFilesCheck class BuildRootCheck(AbstractFilesCheck): def __init__(self, config, output): super().__init__(config, output, r'.*') self.prepare_regex(rpm.expandMacro('%buildroot')) def prepare_regex(self, buildroot): for m in ('name', 'version', 'release', 'NAME', 'VERSION', 'RELEASE'): buildroot = buildroot.replace('%%{%s}' % (m), r'[\w\!-\.]{1,20}') self.build_root_re = re.compile(buildroot) def check_file(self, pkg, filename): if filename.startswith('/usr/lib/debug') or pkg.is_source: return if not stat.S_ISREG(pkg.files[filename].mode): return if pkg.grep(self.build_root_re, filename): self.output.add_info('E', pkg, 'file-contains-buildroot', filename) rpmlint-2.2.0+ds1/rpmlint/checks/ConfigFilesCheck.py000066400000000000000000000021301415540642600223130ustar00rootroot00000000000000from rpmlint.checks.AbstractCheck import AbstractCheck class ConfigFilesCheck(AbstractCheck): """ Check that configuration files are in a proper location and marked as 'noreplace'. """ def check_binary(self, pkg): for filename in pkg.config_files: self._check_non_confdir_files(pkg, filename) self._check_noreplace_files(pkg, filename) def _check_non_confdir_files(self, pkg, fn): """ Check if the configuration file is in /etc or /var directory. Print a warning if it's not. """ if not fn.startswith('/etc/') and not fn.startswith('/var/'): self.output.add_info('W', pkg, 'non-etc-or-var-file-marked-as-conffile', fn) def _check_noreplace_files(self, pkg, fn): """ Check if the configuration file has 'noreplace' flag. Print a warning if there is no 'noreplace' tag. """ if fn not in pkg.noreplace_files: self.output.add_info('W', pkg, 'conffile-without-noreplace-flag', fn) rpmlint-2.2.0+ds1/rpmlint/checks/DBusPolicyCheck.py000066400000000000000000000045161415540642600221520ustar00rootroot00000000000000from xml.dom.minidom import parse from rpmlint.checks.AbstractCheck import AbstractCheck DBUS_DIRECTORIES = ('/etc/dbus-1/system.d/', '/usr/share/dbus-1/system.d/') class DBusPolicyCheck(AbstractCheck): def check(self, pkg): if pkg.is_source: return for f in pkg.files: if f in pkg.ghost_files: continue # catch xml exceptions try: if any(f.startswith(d) for d in DBUS_DIRECTORIES): send_policy_seen = False lf = pkg.dirName() + f xml = parse(lf) for policy in xml.getElementsByTagName('policy'): send_policy_seen = self._check_allow_policy_element(pkg, f, policy) or send_policy_seen self._check_deny_policy_element(pkg, f, policy) if not send_policy_seen: self.output.add_info('E', pkg, 'communication not allowed', f) except Exception as e: self.output.add_info('E', pkg, 'dbus-parsing-exception', 'raised an exception: ' + str(e), f) continue def _check_allow_policy_element(self, pkg, f, policy): send_policy_seen = False for allow in policy.getElementsByTagName('allow'): if ((allow.hasAttribute('send_interface') or allow.hasAttribute('send_member') or allow.hasAttribute('send_path')) and not allow.hasAttribute('send_destination')): send_policy_seen = True self.output.add_info('E', pkg, 'dbus-policy-allow-without-destination', allow.toxml(), f) elif allow.hasAttribute('send_destination'): send_policy_seen = True if (allow.hasAttribute('receive_sender') or allow.hasAttribute('receive_interface')): self.output.add_info('W', pkg, 'dbus-policy-allow-receive', allow.toxml(), f) return send_policy_seen def _check_deny_policy_element(self, pkg, f, policy): for deny in policy.getElementsByTagName('deny'): if (deny.hasAttribute('send_interface') and not deny.hasAttribute('send_destination')): self.output.add_info('E', pkg, 'dbus-policy-deny-without-destination', deny.toxml(), f) rpmlint-2.2.0+ds1/rpmlint/checks/DocCheck.py000066400000000000000000000077541415540642600206510ustar00rootroot00000000000000from pathlib import Path import stat import rpm from rpmlint.checks.AbstractCheck import AbstractCheck from rpmlint.helpers import byte_to_string class DocCheck(AbstractCheck): """ Package documentation checks. """ def check_binary(self, pkg): if not pkg.doc_files: return self._check_executable_docs(pkg) self._check_doc_file_dependencies(pkg) self._check_unwanted_files(pkg) if not self._ignore_pkg(pkg.name): self._check_huge_docs(pkg) @staticmethod def _ignore_pkg(name): if name.startswith('bundle-') or '-devel' in name or '-doc' in name: return True return False def _check_executable_docs(self, pkg): """ Check if the documentation in the package is executable and print an error if it is. """ for f in pkg.doc_files: mode = pkg.files[f].mode if not stat.S_ISREG(mode) or not mode & 0o111: continue path = Path(f) extensions = ['.txt', '.gif', '.jpg', '.html', '.pdf', '.ps', '.pdf.gz', '.ps.gz'] if path.suffix in extensions: self.output.add_info('E', pkg, 'executable-docs', f) for name in ['README', 'NEWS', 'COPYING', 'AUTHORS', 'LICENCE', 'LICENSE']: if path.name.lower() == name.lower(): self.output.add_info('E', pkg, 'executable-docs', f) def _check_doc_file_dependencies(self, pkg): """ Check if docfiles create additional dependencies in the package and print a warning if so. """ files = pkg.files reqs = {} for fname, pkgfile in files.items(): reqs[fname] = [x[0] for x in pkgfile.requires] core_reqs = {} # dependencies of non-doc files doc_reqs = {} # dependencies of doc files for dep in pkg.header.dsFromHeader(): # skip deps which were found by find-requires if dep.Flags() & rpm.RPMSENSE_FIND_REQUIRES != 0: continue core_reqs[dep.N()] = [] # register things which are provided by the package for i in pkg.header[rpm.RPMTAG_PROVIDES]: core_reqs[byte_to_string(i)] = [] for i in files: core_reqs[i] = [] for i in files: if not reqs[i]: continue # skip empty dependencies if i in pkg.doc_files: target = doc_reqs else: target = core_reqs for r in reqs[i]: if r not in target: target[r] = [] target[r].append(i) # go through the calculated requirements of the %doc files for (dep, req_files) in doc_reqs.items(): if dep not in core_reqs: for f in req_files: self.output.add_info('W', pkg, 'doc-file-dependency', f, dep) def _check_unwanted_files(self, pkg): """ Check if docfiles contain unwanted files. Now it checks the presence of the INSTALL file that is often not relevant for the built package. """ for docfile in pkg.doc_files: if docfile.endswith('/INSTALL'): self.output.add_info('W', pkg, 'install-file-in-docs', docfile) def _check_huge_docs(self, pkg): """ Check the size of the documentation in the package and print a warning if it's more than half of the size of the package. """ files = pkg.files complete_size = 0 for _, pkgfile in files.items(): if stat.S_ISREG(pkgfile.mode): complete_size += pkgfile.size doc_size = 0 for f in pkg.doc_files: if stat.S_ISREG(files[f].mode): doc_size += files[f].size if doc_size * 2 >= complete_size and doc_size > 100 * 1024: self.output.add_info('W', pkg, 'package-with-huge-docs: %3d%%' % (doc_size * 100 / complete_size)) rpmlint-2.2.0+ds1/rpmlint/checks/DuplicatesCheck.py000066400000000000000000000074261415540642600222350ustar00rootroot00000000000000import stat from rpmlint.checks.AbstractCheck import AbstractCheck class DuplicatesCheck(AbstractCheck): """ Search for the duplicated files in the package. It uses the following structures: md5s - key: md5 hash of the file - values: files with this hash sizes - key: md5 hash of the file - values: size of the file """ def check(self, pkg): if pkg.is_source: return md5s = {} sizes = {} hardlinks = {} total_dup_size = 0 for fname, pkgfile in pkg.files.items(): if fname in pkg.ghost_files or not stat.S_ISREG(pkgfile.mode): continue # fillup md5s and sizes dicts md5s.setdefault(pkgfile.md5, set()).add(pkgfile) sizes[pkgfile.md5] = pkgfile.size key = (pkgfile.rdev, pkgfile.inode) if key not in hardlinks: hardlinks[key] = 0 hardlinks[key] += 1 # process duplicates for md5_hash in md5s: # obtain the list of the files with the same md5 hash duplicates = md5s[md5_hash] # continue, there is no duplicate if len(duplicates) == 1: continue duplicates = sorted(duplicates, key=lambda x: x.name) first = duplicates.pop() first_is_config = False if first.name in pkg.config_files: first_is_config = True prefix = self._get_prefix(first) # 1 (first) + number of others - number of hard links # (keeps track of how many directories have entries for this file) # diff is a number of files that are duplicates but not hard-links key = (first.rdev, first.inode) diff = 1 + len(duplicates) - hardlinks[key] if diff <= 0: # now we have just hard-links in duplicates for duplicate in duplicates: if prefix != self._get_prefix(duplicate): self.output.add_info('E', pkg, 'hardlink-across-partition', first.name, duplicate.name) if first_is_config and duplicate.name in pkg.config_files: self.output.add_info('E', pkg, 'hardlink-across-config-files', first.name, duplicate.name) continue # now we know that there are some duplicates that are not links for duplicate in duplicates: if prefix != self._get_prefix(duplicate): # if the duplicate is in a different prefix, we can ignore # it since it can't be linked anyway diff = diff - 1 # if there is still a positive diff (i.e. there is a duplicate that # is not a link and wasn't ignored by the previous step), # report a warning if sizes[md5_hash] and diff > 0: self.output.add_info('W', pkg, 'files-duplicate', first.name, ':'.join([x.name for x in duplicates])) total_dup_size += sizes[md5_hash] * diff # check the overall size of the duplicates and print an error if it's # too much if total_dup_size > 100000: self.output.add_info('E', pkg, 'files-duplicated-waste', total_dup_size) @staticmethod def _get_prefix(pkgfile): """Return first two directories in the given path.""" pathlist = str.split(pkgfile.name, '/') if len(pathlist) == 3: return '/'.join(pathlist[0:2]) return '/'.join(pathlist[0:3]) rpmlint-2.2.0+ds1/rpmlint/checks/ErlangCheck.py000066400000000000000000000025101415540642600213350ustar00rootroot00000000000000import re from pybeam import BeamFile from rpm import expandMacro from rpmlint.checks.AbstractCheck import AbstractFilesCheck from rpmlint.helpers import byte_to_string class ErlangCheck(AbstractFilesCheck): def __init__(self, config, output): super().__init__(config, output, r'.*?\.beam$') build_dir = expandMacro('%_builddir') self.source_re = re.compile(build_dir) def check_file(self, pkg, filename): try: beam = BeamFile(pkg.files[filename].path) if beam.compileinfo is None: self.output.add_info('W', pkg, 'beam-compile-info-missed', filename) return compile_state = byte_to_string(str(beam.compileinfo['source'])) if 'debug_info' not in beam.compileinfo['options']: self.output.add_info('E', pkg, 'beam-compiled-without-debuginfo', filename) # This can't be an error as builddir can be user specific and vary between users # it could be error in OBS where all the builds are done by user abuild, not in # general. if not self.source_re.match(compile_state): self.output.add_info('W', pkg, 'beam-was-not-recompiled', filename, compile_state) except Exception: self.output.add_info('E', pkg, 'pybeam-failed', filename) rpmlint-2.2.0+ds1/rpmlint/checks/FHSCheck.py000066400000000000000000000064311415540642600205530ustar00rootroot00000000000000import re from rpmlint.checks.AbstractCheck import AbstractCheck class FHSCheck(AbstractCheck): """ Validate that binary files are packaged according to FHS. We follow FHS 3.0 specification that can be found at http://refspecs.linuxfoundation.org/FHS_3.0/fhs-3.0.pdf FHS_usr_subdirs lists allowed directories in /usr (FHS chapter 4.2 and 4.3) FHS_var_subdirs lists allowed directories in /var (FHS chapter 5.2 and 5.3) """ usr_regex = re.compile('^/usr/([^/]+)') FHS_usr_subdirs = ('bin', 'lib', 'local', 'sbin', 'share', 'games', 'include', 'libexec', 'lib64', 'src', 'spool', 'tmp') var_regex = re.compile('^/var/([^/]+)') FHS_var_subdirs = ('cache', 'lib', 'local', 'lock', 'log', 'opt', 'run', 'spool', 'tmp', 'account', 'crash', 'games', 'mail', 'yp') def __init__(self, config, output): super().__init__(config, output) self.output.error_details.update(fhs_details_dict) def check_binary(self, pkg): var_list = [] usr_list = [] for fname in pkg.files: usr_path = self.usr_regex.search(fname) if usr_path: # Run tests for /usr directory usr_file = usr_path.group(1) self._check_usr_standard_dir(usr_file, pkg, usr_list) continue var_path = self.var_regex.search(fname) if var_path: # Run tests for /var directory var_file = var_path.group(1) self._check_var_standard_dir(var_file, pkg, var_list) def _check_usr_standard_dir(self, usr_file, pkg, usr_list): """ Check if the file is in valid subdirectory of /usr. FHS 3.0 says: "Large software packages must not use a direct subdirectory under the /usr hierarchy." Check if this package contains a directory in /usr that is not mentioned in FHS (FHS_usr_subdirs). Refer to http://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch04.html for details. """ if usr_file not in self.FHS_usr_subdirs and usr_file not in usr_list: usr_list.append(usr_file) self.output.add_info('W', pkg, 'non-standard-dir-in-usr', usr_file) def _check_var_standard_dir(self, var_file, pkg, var_list): """ Check if the file is in valid subdirectory of /var. FHS 3.0 says: "Applications must generally not add directories to the top level of /var. Such directories should only be added if they have some system-wide implication, and in consultation with the FHS mailing list." Refer to http://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch05.htm for details. """ if var_file not in self.FHS_var_subdirs and var_file not in var_list: var_list.append(var_file) self.output.add_info('W', pkg, 'non-standard-dir-in-var', var_file) fhs_details_dict = { 'non-standard-dir-in-usr': """Your package is creating a non-standard subdirectory in /usr. The standard directories are: %s.""" % ', '.join(FHSCheck.FHS_usr_subdirs), 'non-standard-dir-in-var': """Your package is creating a non-standard subdirectory in /var. The standard directories are: %s.""" % ', '.join(FHSCheck.FHS_var_subdirs), } rpmlint-2.2.0+ds1/rpmlint/checks/FilesCheck.py000066400000000000000000001403511415540642600211750ustar00rootroot00000000000000############################################################################# # File : FilesCheck.py # Package : rpmlint # Author : Frederic Lepied # Created on : Mon Oct 4 19:32:49 1999 # Purpose : test various aspects on files: locations, owner, groups, # permission, setuid, setgid... ############################################################################# from datetime import datetime import os from pathlib import Path import re import stat import rpm from rpmlint.checks.AbstractCheck import AbstractCheck from rpmlint.helpers import byte_to_string from rpmlint.pkg import is_utf8, is_utf8_bytestr # must be kept in sync with the filesystem package STANDARD_DIRS = ( '/', '/bin', '/boot', '/etc', '/etc/X11', '/etc/opt', '/etc/profile.d', '/etc/skel', '/etc/xinetd.d', '/home', '/lib', '/lib/modules', '/lib64', '/media', '/mnt', '/mnt/cdrom', '/mnt/disk', '/mnt/floppy', '/opt', '/proc', '/root', '/run', '/sbin', '/selinux', '/srv', '/sys', '/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/lib/X11', '/usr/lib/games', '/usr/lib/gcc-lib', '/usr/lib/menu', '/usr/lib64', '/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/share', '/usr/local/share/man', '/usr/local/share/man/man1', '/usr/local/share/man/man2', '/usr/local/share/man/man3', '/usr/local/share/man/man4', '/usr/local/share/man/man5', '/usr/local/share/man/man6', '/usr/local/share/man/man7', '/usr/local/share/man/man8', '/usr/local/share/man/man9', '/usr/local/share/man/mann', '/usr/local/src', '/usr/sbin', '/usr/share', '/usr/share/dict', '/usr/share/doc', '/usr/share/icons', '/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/log', '/var/mail', '/var/nis', '/var/opt', '/var/preserve', '/var/spool', '/var/spool/mail', '/var/tmp', ) compressions = r'\.(gz|z|Z|zip|bz2|lzma|xz|zst)' sub_bin_regex = re.compile(r'^(/usr)?/s?bin/\S+/') backup_regex = re.compile(r'(~|\#[^/]+\#|((\.orig|\.rej)(' + compressions + ')?))$') compr_regex = re.compile(compressions + r'$') absolute_regex = re.compile(r'^/([^/]+)') absolute2_regex = re.compile(r'^/?([^/]+)') points_regex = re.compile(r'^\.\./(.*)') doc_regex = re.compile(r'^/usr(/share|/X11R6)?/(doc|man|info)/|^/usr/share/gnome/help') bin_regex = re.compile(r'^/(?:usr/(?:s?bin|games)|s?bin)/(.*)') includefile_regex = re.compile(r'\.(c|h)(pp|xx)?$', re.IGNORECASE) develfile_regex = re.compile(r'\.(a|cmxa?|mli?|gir)$') buildconfigfile_regex = re.compile(r'(\.pc|/bin/.+-config)$') # room for improvement with catching more -R, but also for false positives... buildconfig_rpath_regex = re.compile(r'(?:-rpath|Wl,-R)\b') sofile_regex = re.compile(r'/lib(64)?/(.+/)?lib[^/]+\.so$') devel_regex = re.compile(r'(.*)-(debug(info|source)?|devel|headers|source|static)$') debuginfo_package_regex = re.compile(r'-debug(info)?$') debugsource_package_regex = re.compile(r'-debugsource$') lib_regex = re.compile(r'/lib(?:64)?/lib[A-Za-z0-9](?:(?:|[\w\-\.]*[A-Za-z0-9])\.so\.[\w+\.]+|\w*-\d(?:|[\w\-\.]*[A-Za-z0-9])\.so)$') # see commit log for detail ldconfig_regex = re.compile(r'^[^#]*ldconfig', re.MULTILINE) depmod_regex = re.compile(r'^[^#]*depmod', re.MULTILINE) install_info_regex = re.compile(r'^[^#]*install-info', re.MULTILINE) perl_temp_file_regex = re.compile(r'.*perl.*/(\.packlist|perllocal\.pod)$') scm_regex = re.compile( r'/(?:RCS|CVS)/[^/]+$|/\.(?:bzr|cvs|git|hg|svn)ignore$|' r',v$|/\.hgtags$|/\.(?:bzr|git|hg|svn)/|/(?:\.arch-ids|{arch})/') games_path_regex = re.compile(r'^/usr(/lib(64)?)?/games/') logrotate_regex = re.compile(r'^/etc/logrotate\.d/(.*)') kernel_modules_regex = re.compile(r'^(?:/usr)/lib/modules/([0-9]+\.[0-9]+\.[0-9]+[^/]*?)/') kernel_package_regex = re.compile(r'^kernel(22)?(-)?(smp|enterprise|bigmem|secure|BOOT|i686-up-4GB|p3-smp-64GB)?') normal_zero_length_regex = re.compile(r'^/etc/security/console\.apps/' r'|/\.nosearch$' r'|/__init__\.py$' r'|/py\.typed$' # https://www.python.org/dev/peps/pep-0561/#packaging-type-information r'|\.dist-info/REQUESTED$' # https://www.python.org/dev/peps/pep-0376/#requested r'|/gem\.build_complete$') perl_regex = re.compile(r'^/usr/lib/perl5/(?:vendor_perl/)?([0-9]+\.[0-9]+)\.([0-9]+)/') python_regex = re.compile(r'^/usr/lib(?:64)?/python([.0-9]+)/') python_bytecode_regex_pep3147 = re.compile(r'^(.*)/__pycache__/(.*?)\.([^.]+)(\.opt-[12])?\.py[oc]$') python_bytecode_regex = re.compile(r'^(.*)(\.py[oc])$') log_regex = re.compile(r'^/var/log/[^/]+$') lib_path_regex = re.compile(r'^(/usr(/X11R6)?)?/lib(64)?') lib_package_regex = re.compile(r'^(lib|.+-libs)') hidden_file_regex = re.compile(r'/\.[^/]*$') manifest_perl_regex = re.compile(r'^/usr/share/doc/perl-.*/MANIFEST(\.SKIP)?$') shebang_regex = re.compile(br'^#!\s*(\S+)(.*?)$', re.M) interpreter_regex = re.compile(r'^/(?:usr/)?(?:s?bin|games|libexec(?:/.+)?|(?:lib(?:64)?|share)/.+)/([^/]+)$') script_regex = re.compile(r'^/((usr/)?s?bin|etc/(rc\.d/init\.d|X11/xinit\.d|cron\.(hourly|daily|monthly|weekly)))/') sourced_script_regex = re.compile(r'^/etc/(bash_completion\.d|profile\.d)/') filesys_packages = ['filesystem'] # TODO: make configurable? quotes_regex = re.compile(r'[\'"]+') start_certificate_regex = re.compile(r'^-----BEGIN CERTIFICATE-----$') start_private_key_regex = re.compile(r'^----BEGIN PRIVATE KEY-----$') non_readable_regexs = (re.compile(r'^/var/log/'), re.compile(r'^/etc/(g?shadow-?|securetty)$')) man_base_regex = re.compile(r'^/usr(?:/share)?/man(?:/overrides)?/man[^/]+/(.+)\.[1-9n]') fsf_license_regex = re.compile(br'(GNU((\s+(Library|Lesser|Affero))?(\s+General)?\s+Public|\s+Free\s+Documentation)\s+Licen[cs]e|(GP|FD)L)', re.IGNORECASE) fsf_wrong_address_regex = re.compile(br'(675\s+Mass\s+Ave|59\s+Temple\s+Place|Franklin\s+Steet|02139|02111-1307)', re.IGNORECASE) scalable_icon_regex = re.compile(r'^/usr(?:/local)?/share/icons/.*/scalable/') tcl_regex = re.compile(r'^/usr/lib(64)?/([^/]+/)?pkgIndex\.tcl') printable_extended_ascii = b'\n\r\t\f\b' printable_extended_ascii += bytes(range(32, 256)) # See Python sources for a full list of the values here. # https://github.com/python/cpython/blob/master/Lib/importlib/_bootstrap_external.py # https://github.com/python/cpython/blob/2.7/Python/import.c # https://github.com/python/cpython/commit/93602e3af70d3b9f98ae2da654b16b3382b68d50 _python_magic_values = { '2.2': [60717], '2.3': [62011], '2.4': [62061], '2.5': [62131], '2.6': [62161], '2.7': [62211], '3.0': [3130], '3.1': [3150], '3.2': [3180], '3.3': [3230], '3.4': [3310], '3.5': [3350, 3351], # 3350 for < 3.5.2 '3.6': [3379], '3.7': [3390, 3391, 3392, 3393, 3394], } def get_expected_pyc_magic(path, python_default_version): """ .pyc/.pyo files embed a 4-byte magic value identifying which version of the python bytecode ABI they are for. Given a path to a .pyc/.pyo file, return a (magic ABI values, python version) tuple. For example, '/usr/lib/python3.1/foo.pyc' should return (3151, '3.1'). The first value will be None if the python version was not resolved from the given pathname and the PythonDefaultVersion configuration variable is not set, or if we don't know the magic ABI values for the python version (no matter from which source the version came from). The second value will be None if a python version could not be resolved from the given pathname. """ ver_from_path = None m = python_regex.search(path) if m: ver_from_path = m.group(1) expected_version = ver_from_path or python_default_version expected_magic_values = _python_magic_values.get(expected_version) if not expected_magic_values: return (None, ver_from_path) # In Python 2, if Py_UnicodeFlag is set, Python's import code uses a value # one higher, but this is off by default. In Python 3.0 and 3.1 (but no # longer in 3.2), it always uses the value one higher: if expected_version[:3] in ('3.0', '3.1'): expected_magic_values = [x + 1 for x in expected_magic_values] return (expected_magic_values, ver_from_path) def py_demarshal_long(b): """ Counterpart to Python's PyMarshal_ReadLongFromFile, operating on the bytes in a string. """ if isinstance(b, str): b = map(ord, b) return (b[0] + (b[1] << 8) + (b[2] << 16) + (b[3] << 24)) def pyc_magic_from_chunk(chunk): """From given chunk (beginning of the file), return Python magic number""" return py_demarshal_long(chunk[:4]) & 0xffff def pyc_mtime_from_chunk(chunk): """From given chunk (beginning of the file), return mtime or None From Python 3.7, mtime is not always present. See https://www.python.org/dev/peps/pep-0552/#specification """ magic = pyc_magic_from_chunk(chunk) second = py_demarshal_long(chunk[4:8]) if magic >= _python_magic_values['3.7'][0]: if second == 0: return py_demarshal_long(chunk[8:12]) return None # No mtime saved, TODO check hashes instead return second def python_bytecode_to_script(path): """ Given a python bytecode path, give the path of the .py file (or None if not python bytecode). """ res = python_bytecode_regex_pep3147.search(path) if res: return res.group(1) + '/' + res.group(2) + '.py' res = python_bytecode_regex.search(path) if res: return res.group(1) + '.py' return None def script_interpreter(chunk): res = shebang_regex.search(chunk) if chunk else None return (byte_to_string(res.group(1)), byte_to_string(res.group(2)).strip()) \ if res and res.start() == 0 else (None, '') class FilesCheck(AbstractCheck): man_regex = re.compile(r'/man(?:\d[px]?|n)/') info_regex = re.compile(r'(/usr/share|/usr)/info/') def __init__(self, config, output): super().__init__(config, output) self.use_debugsource = self.config.configuration['UseDebugSource'] self.games_group_regex = re.compile(self.config.configuration['RpmGamesGroup']) self.dangling_exceptions = self.config.configuration['DanglingSymlinkExceptions'] for item in self.dangling_exceptions.values(): item['path'] = re.compile(item['path']) self.module_rpms_ok = self.config.configuration['KernelModuleRPMsOK'] self.python_default_version = self.config.configuration['PythonDefaultVersion'] self.perl_version_trick = self.config.configuration['PerlVersionTrick'] self.skipdocs_regex = re.compile(self.config.configuration['SkipDocsRegexp'], re.IGNORECASE) self.meta_package_regex = re.compile(self.config.configuration['MetaPackageRegexp']) self.use_relative_symlinks = self.config.configuration['UseRelativeSymlinks'] self.standard_groups = self.config.configuration['StandardGroups'] self.standard_users = self.config.configuration['StandardUsers'] self.disallowed_dirs = self.config.configuration['DisallowedDirs'] self.compress_ext = self.config.configuration['CompressExtension'] self.output.error_details.update({ 'non-standard-uid': """A file in this package is owned by a non standard user. Standard users are: %s.""" % ', '.join(self.standard_users), 'non-standard-gid': """A file in this package is owned by a non standard group. Standard groups are: %s.""" % ', '.join(self.standard_groups), 'manpage-not-compressed': """This manual page is not compressed with the %s compression method (does not have the %s extension). If the compression does not happen automatically when the package is rebuilt, make sure that you have the appropriate rpm helper and/or config packages for your target distribution installed and try rebuilding again; if it still does not happen automatically, you can compress this file in the %%install section of the spec file.""" % (self.compress_ext, self.compress_ext), 'infopage-not-compressed': """This info page is not compressed with the %s compression method (does not have the %s extension). If the compression does not happen automatically when the package is rebuilt, make sure that you have the appropriate rpm helper and/or config packages for your target distribution installed and try rebuilding again; if it still does not happen automatically, you can compress this file in the %%install section of the spec file.""" % (self.compress_ext, self.compress_ext), }) for i in self.disallowed_dirs: self.output.error_details.update({'dir-or-file-in-%s' % '-'.join(i.split('/')[1:]): """A file in the package is located in %s. It's not permitted for packages to install files in this directory.""" % i}) def peek(self, filename, pkg, length=1024): """ Peek into a file, return a chunk from its beginning and a flag if it seems to be a text file. """ chunk = None try: with open(filename, 'rb') as fobj: chunk = fobj.read(length) except IOError as e: # eg. https://bugzilla.redhat.com/209876 self.output.add_info('W', pkg, 'read-error', e) return (chunk, False) if b'\0' in chunk: return (chunk, False) if not chunk: # Empty files are considered text return (chunk, True) fl = filename.lower() # PDF's are binary but often detected as text by the algorithm below if fl.endswith('.pdf') and chunk.startswith(b'%PDF-'): return (chunk, False) # Ditto RDoc RI files if fl.endswith('.ri') and '/ri/' in fl: return (chunk, False) # And Sphinx inventory files if fl.endswith('.inv') and chunk.startswith(b'# Sphinx inventory'): return (chunk, False) # Binary if control chars are > 30% of the string control_chars = chunk.translate(None, printable_extended_ascii) nontext_ratio = float(len(control_chars)) / float(len(chunk)) istext = nontext_ratio <= 0.30 return (chunk, istext) def check(self, pkg): for filename in pkg.header[rpm.RPMTAG_FILENAMES] or (): if not is_utf8_bytestr(filename): self.output.add_info('E', pkg, 'filename-not-utf8', byte_to_string(filename)) # Rest of the checks are for binary packages only if pkg.is_source: return files = pkg.files # Check if the package is a development package devel_pkg = devel_regex.search(pkg.name) if not devel_pkg: for p in pkg.provides: if devel_regex.search(p[0]): devel_pkg = True break deps = pkg.requires + pkg.prereq config_files = pkg.config_files ghost_files = pkg.ghost_files req_names = pkg.req_names lib_package = lib_package_regex.search(pkg.name) is_kernel_package = kernel_package_regex.search(pkg.name) debuginfo_package = debuginfo_package_regex.search(pkg.name) debugsource_package = debugsource_package_regex.search(pkg.name) # report these errors only once perl_dep_error = False python_dep_error = False lib_file = False non_lib_file = None log_files = [] logrotate_file = False debuginfo_srcs = False debuginfo_debugs = False if not lib_package and not pkg.doc_files: self.output.add_info('W', pkg, 'no-documentation') if files: if self.meta_package_regex.search(pkg.name): self.output.add_info('W', pkg, 'file-in-meta-package') elif debuginfo_package or debugsource_package: self.output.add_info('E', pkg, 'empty-debuginfo-package') # Prefetch scriptlets, strip quotes from them (#169) postin = pkg[rpm.RPMTAG_POSTIN] or \ pkg.scriptprog(rpm.RPMTAG_POSTINPROG) if postin: postin = quotes_regex.sub('', postin) postun = pkg[rpm.RPMTAG_POSTUN] or \ pkg.scriptprog(rpm.RPMTAG_POSTUNPROG) if postun: postun = quotes_regex.sub('', postun) # Unique (rdev, inode) combinations hardlinks = {} # All executable files from standard bin dirs (basename => [paths]) # Hack: basenames with empty paths links are symlinks (not subject # to duplicate binary check, but yes for man page existence check) bindir_exes = {} # All man page 'base' names (without section etc extensions) man_basenames = set() for f, pkgfile in files.items(): fpath = Path(f) mode = pkgfile.mode user = pkgfile.user group = pkgfile.group link = pkgfile.linkto size = pkgfile.size rdev = pkgfile.rdev inode = pkgfile.inode is_doc = f in pkg.doc_files nonexec_file = False self._check_manpage_compressed(pkg, f) self._check_infopage_compressed(pkg, f) for match in self.macro_regex.findall(f): self.output.add_info('W', pkg, 'unexpanded-macro', f, match) if user not in self.standard_users: self.output.add_info('W', pkg, 'non-standard-uid', f, user) if group not in self.standard_groups: self.output.add_info('W', pkg, 'non-standard-gid', f, group) if not self.module_rpms_ok and kernel_modules_regex.search(f) and not \ is_kernel_package: self.output.add_info('E', pkg, 'kernel-modules-not-in-kernel-packages', f) for i in self.disallowed_dirs: if f.startswith(i): self.output.add_info('E', pkg, 'dir-or-file-in-%s' % '-'.join(i.split('/')[1:]), f) if f.startswith('/run/'): if f not in ghost_files: self.output.add_info('W', pkg, 'non-ghost-in-run', f) elif f.startswith('/etc/systemd/system/'): self.output.add_info('W', pkg, 'systemd-unit-in-etc', f) elif f.startswith('/etc/udev/rules.d/'): self.output.add_info('W', pkg, 'udev-rule-in-etc', f) elif f.startswith('/etc/tmpfiles.d/'): self.output.add_info('W', pkg, 'tmpfiles-conf-in-etc', f) elif sub_bin_regex.search(f): self.output.add_info('E', pkg, 'subdir-in-bin', f) elif '/site_perl/' in f: self.output.add_info('W', pkg, 'siteperl-in-perl-module', f) if backup_regex.search(f): self.output.add_info('E', pkg, 'backup-file-in-package', f) elif scm_regex.search(f): self.output.add_info('E', pkg, 'version-control-internal-file', f) elif f.endswith('/.htaccess'): self.output.add_info('E', pkg, 'htaccess-file', f) elif (hidden_file_regex.search(f) and not f.startswith('/etc/skel/') and not f.endswith('/.build-id') and not f.endswith('/.cargo-checksum.json')): self.output.add_info('W', pkg, 'hidden-file-or-dir', f) elif manifest_perl_regex.search(f): self.output.add_info('W', pkg, 'manifest-in-perl-module', f) elif f == '/usr/info/dir' or f == '/usr/share/info/dir': self.output.add_info('E', pkg, 'info-dir-file', f) if fpath.name == 'Makefile.am' and str(fpath.with_suffix('.in')) in files and is_doc: self.output.add_info('W', pkg, 'makefile-junk', f) res = logrotate_regex.search(f) if res: logrotate_file = True if res.group(1) != pkg.name: self.output.add_info('E', pkg, 'incoherent-logrotate-file', f) deps = [x[0] for x in pkg.requires + pkg.recommends + pkg.suggests] if res and not ('logrotate' in deps) and pkg.name != 'logrotate': self.output.add_info('E', pkg, 'missing-dependency-to-logrotate', 'for logrotate script', f) if f.startswith('/etc/cron.') \ and not ('crontabs' in deps) and pkg.name != 'crontabs': self.output.add_info('E', pkg, 'missing-dependency-to-crontabs', 'for cron script', f) if f.startswith('/etc/xinet.d/') \ and not ('xinetd' in deps) and pkg.name != 'xinetd': self.output.add_info('E', pkg, 'missing-dependency-to-xinetd', 'for xinet.d script', f) if link != '': ext = compr_regex.search(link) if ext: if not re.compile(r'\.%s$' % ext.group(1)).search(f): self.output.add_info('E', pkg, 'compressed-symlink-with-wrong-ext', f, link) perm = mode & 0o7777 mode_is_exec = mode & 0o111 if log_regex.search(f): log_files.append(f) # Hardlink check for hardlink in hardlinks.get((rdev, inode), ()): if Path(hardlink).parent != Path(f).parent: self.output.add_info('W', pkg, 'cross-directory-hard-link', f, hardlink) hardlinks.setdefault((rdev, inode), []).append(f) # normal file check if stat.S_ISREG(mode): # set[ug]id bit check if stat.S_ISGID & mode or stat.S_ISUID & mode: if stat.S_ISUID & mode: self.output.add_info('E', pkg, 'setuid-binary', f, user, '%o' % perm) if stat.S_ISGID & mode: if not (group == 'games' and (games_path_regex.search(f) or self.games_group_regex.search( pkg[rpm.RPMTAG_GROUP]))): self.output.add_info('E', pkg, 'setgid-binary', f, group, '%o' % perm) if mode & 0o777 != 0o755: self.output.add_info('E', pkg, 'non-standard-executable-perm', f, '%o' % perm) if not devel_pkg: if lib_path_regex.search(f): lib_file = True elif not is_doc: non_lib_file = f if log_regex.search(f): nonexec_file = True if user != 'root': self.output.add_info('E', pkg, 'non-root-user-log-file', f, user) if group != 'root': self.output.add_info('E', pkg, 'non-root-group-log-file', f, group) if f not in ghost_files: self.output.add_info('E', pkg, 'non-ghost-file', f) chunk = None istext = False res = None try: res = os.access(pkgfile.path, os.R_OK) except UnicodeError as e: # e.g. non-ASCII, C locale, python 3 self.output.add_info('W', pkg, 'inaccessible-filename', f, e) else: if res: (chunk, istext) = self.peek(pkgfile.path, pkg) (interpreter, interpreter_args) = script_interpreter(chunk) if doc_regex.search(f): if not interpreter: nonexec_file = True if not is_doc: self.output.add_info('E', pkg, 'not-listed-as-documentation', f) if devel_pkg and f.endswith('.typelib'): self.output.add_info('E', pkg, 'non-devel-file-in-devel-package', f) # check ldconfig call in %post and %postun if lib_regex.search(f): if devel_pkg and not (sofile_regex.search(f) and stat.S_ISLNK(mode)): self.output.add_info('E', pkg, 'non-devel-file-in-devel-package', f) if not postin: self.output.add_info('E', pkg, 'library-without-ldconfig-postin', f) else: if not ldconfig_regex.search(postin): self.output.add_info('E', pkg, 'postin-without-ldconfig', f) if not postun: self.output.add_info('E', pkg, 'library-without-ldconfig-postun', f) else: if not ldconfig_regex.search(postun): self.output.add_info('E', 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( r'\bdepmod\s+-a.*F\s+/boot/System\.map-' + re.escape(kernel_version) + r'\b.*\b' + re.escape(kernel_version) + r'\b', re.MULTILINE | re.DOTALL) if not postin or not depmod_regex.search(postin): self.output.add_info('E', pkg, 'module-without-depmod-postin', f) # check that we run depmod on the right kernel elif not kernel_version_regex.search(postin): self.output.add_info('E', pkg, 'postin-with-wrong-depmod', f) if not postun or not depmod_regex.search(postun): self.output.add_info('E', pkg, 'module-without-depmod-postun', f) # check that we run depmod on the right kernel elif not kernel_version_regex.search(postun): self.output.add_info('E', pkg, 'postun-with-wrong-depmod', f) # check install-info call in %post and %postun if f.startswith('/usr/share/info/'): if not postin: self.output.add_info('E', pkg, 'info-files-without-install-info-postin', f) elif not install_info_regex.search(postin): self.output.add_info('E', pkg, 'postin-without-install-info', f) preun = pkg[rpm.RPMTAG_PREUN] or \ pkg.scriptprog(rpm.RPMTAG_PREUNPROG) if not postun and not preun: self.output.add_info('E', pkg, 'info-files-without-install-info-postun', f) elif not ((postun and install_info_regex.search(postun)) or (preun and install_info_regex.search(preun))): self.output.add_info('E', pkg, 'postin-without-install-info', f) # check perl temp file if perl_temp_file_regex.search(f): self.output.add_info('W', pkg, 'perl-temp-file', f) is_buildconfig = istext and buildconfigfile_regex.search(f) # check rpaths in buildconfig files if is_buildconfig: ln = pkg.grep(buildconfig_rpath_regex, f) if ln: self.output.add_info('E', pkg, 'rpath-in-buildconfig', f, 'lines', ln) # look for man pages res = man_base_regex.search(f) if res: man_basenames.add(res.group(1)) res = bin_regex.search(f) if res: if not mode_is_exec: self.output.add_info('W', pkg, 'non-executable-in-bin', f, '%o' % perm) else: exe = res.group(1) if '/' not in exe: bindir_exes.setdefault(exe, []).append(f) if (not devel_pkg and not is_doc and (is_buildconfig or includefile_regex.search(f) or develfile_regex.search(f))): self.output.add_info('W', pkg, 'devel-file-in-non-devel-package', f) if mode & 0o444 != 0o444 and perm & 0o7000 == 0: ok_nonreadable = False for regex in non_readable_regexs: if regex.search(f): ok_nonreadable = True break if not ok_nonreadable: self.output.add_info('E', pkg, 'non-readable', f, '%o' % perm) if size == 0 and not normal_zero_length_regex.search(f) and \ f not in ghost_files: self.output.add_info('E', pkg, 'zero-length', f) if mode & stat.S_IWOTH: self.output.add_info('E', pkg, 'world-writable', f, '%o' % perm) if not perl_dep_error: res = perl_regex.search(f) if res: if self.perl_version_trick: vers = res.group(1) + '.' + 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) or f'perl(:MODULE_COMPAT_{vers})' in deps): self.output.add_info('E', pkg, 'no-dependency-on', 'perl-base', vers) perl_dep_error = True if not python_dep_error: res = python_regex.search(f) if (res and not any((pkg.check_versioned_dep(dep, res.group(1)) for dep in ( 'python', 'python-base', 'python(abi)')))): self.output.add_info('E', pkg, 'no-dependency-on', 'python-base', res.group(1)) python_dep_error = True source_file = python_bytecode_to_script(f) if source_file: if source_file in files: if chunk: # Verify that the magic ABI value embedded in the # .pyc header is correct found_magic = pyc_magic_from_chunk(chunk) exp_magic, exp_version = get_expected_pyc_magic(f, self.python_default_version) if exp_magic and found_magic not in exp_magic: found_version = 'unknown' for (pv, pm) in _python_magic_values.items(): if found_magic in pm: found_version = pv break # If expected version was from the file path, # issue # an error, otherwise a warning. msg = (pkg, 'python-bytecode-wrong-magic-value', f, 'expected %s (%s), found %d (%s)' % (' or '.join(map(str, exp_magic)), exp_version or self.python_default_version, found_magic, found_version)) if exp_version is not None: self.output.add_info('E', *msg) else: self.output.add_info('W', *msg) # Verify that the timestamp embedded in the .pyc # header matches the mtime of the .py file: pyc_timestamp = pyc_mtime_from_chunk(chunk) # If it's a symlink, check target file mtime. srcfile = pkg.readlink(files[source_file]) if not srcfile: self.output.add_info('W', pkg, 'python-bytecode-without-source', f) elif (pyc_timestamp is not None and pyc_timestamp != srcfile.mtime): cts = datetime.fromtimestamp( pyc_timestamp).isoformat() sts = datetime.fromtimestamp( srcfile.mtime).isoformat() self.output.add_info('E', pkg, 'python-bytecode-inconsistent-mtime', f, cts, srcfile.name, sts) else: self.output.add_info('W', pkg, 'python-bytecode-without-source', f) # normal executable check if mode & stat.S_IXUSR and perm != 0o755: self.output.add_info('E', pkg, 'non-standard-executable-perm', f, '%o' % perm) if mode_is_exec: if f in config_files: self.output.add_info('E', pkg, 'executable-marked-as-config-file', f) if not nonexec_file: # doc_regex and log_regex checked earlier, no match, # check rest of usual cases here. Sourced scripts have # their own check, so disregard them here. nonexec_file = f.endswith('.pc') or \ compr_regex.search(f) or \ includefile_regex.search(f) or \ develfile_regex.search(f) or \ logrotate_regex.search(f) if nonexec_file: self.output.add_info('W', pkg, 'spurious-executable-perm', f) elif f.startswith('/etc/') and f not in config_files and \ f not in ghost_files: self.output.add_info('W', pkg, 'non-conffile-in-etc', f) if pkg.arch == 'noarch' and f.startswith('/usr/lib64/python'): self.output.add_info('E', pkg, 'noarch-python-in-64bit-path', f) if debuginfo_package: if f.endswith('.debug'): debuginfo_debugs = True else: debuginfo_srcs = True if f.endswith('.svgz') and f[0:-1] not in files \ and scalable_icon_regex.search(f): self.output.add_info('W', pkg, 'gzipped-svg-icon', f) if f.endswith('.pem') and f not in ghost_files: if pkg.grep(start_certificate_regex, f): self.output.add_info('W', pkg, 'pem-certificate', f) if pkg.grep(start_private_key_regex, f): self.output.add_info('E', pkg, 'pem-private-key', f) if tcl_regex.search(f): self.output.add_info('E', pkg, 'tcl-extension-file', f) # text file checks if istext: # ignore perl module shebang -- TODO: disputed... if f.endswith('.pm'): interpreter = None # sourced scripts should not be executable if sourced_script_regex.search(f): if interpreter: self.output.add_info('E', pkg, 'sourced-script-with-shebang', f, interpreter, interpreter_args) if mode_is_exec: self.output.add_info('E', pkg, 'executable-sourced-script', f, '%o' % perm) # ...but executed ones should elif interpreter or mode_is_exec or script_regex.search(f): if interpreter: res = interpreter_regex.search(interpreter) if (mode_is_exec or script_regex.search(f)): if res and res.group(1) == 'env': self.output.add_info('E', pkg, 'env-script-interpreter', f, interpreter, interpreter_args) elif not res: self.output.add_info('E', pkg, 'wrong-script-interpreter', f, interpreter, interpreter_args) elif not nonexec_file and not \ (lib_path_regex.search(f) and f.endswith('.la')): self.output.add_info('E', pkg, 'script-without-shebang', f) if not mode_is_exec and not is_doc and \ interpreter and interpreter.startswith('/'): self.output.add_info('E', pkg, 'non-executable-script', f, '%o' % perm, interpreter, interpreter_args) if b'\r' in chunk: self.output.add_info('E', pkg, 'wrong-script-end-of-line-encoding', f) elif is_doc and not self.skipdocs_regex.search(f): if b'\r' in chunk: self.output.add_info('W', pkg, 'wrong-file-end-of-line-encoding', f) # We check only doc text files for UTF-8-ness; # checking everything may be slow and can generate # lots of unwanted noise. if not is_utf8(pkgfile.path): self.output.add_info('W', pkg, 'file-not-utf8', f) if fsf_license_regex.search(chunk) and \ fsf_wrong_address_regex.search(chunk): self.output.add_info('E', pkg, 'incorrect-fsf-address', f) elif is_doc and chunk and compr_regex.search(f): ff = compr_regex.sub('', f) if not self.skipdocs_regex.search(ff): # compressed docs, eg. info and man files etc if not is_utf8(pkgfile.path): self.output.add_info('W', pkg, 'file-not-utf8', f) # normal dir check elif stat.S_ISDIR(mode): if mode & 0o1002 == 2: # world writable w/o sticky bit self.output.add_info('E', pkg, 'world-writable', f, '%o' % perm) if perm != 0o755: self.output.add_info('E', pkg, 'non-standard-dir-perm', f, '%o' % perm) if pkg.name not in filesys_packages and f in STANDARD_DIRS: self.output.add_info('E', pkg, 'standard-dir-owned-by-package', f) if hidden_file_regex.search(f) and not f.endswith('/.build-id'): self.output.add_info('W', pkg, 'hidden-file-or-dir', f) # symbolic link check elif stat.S_ISLNK(mode): is_so = sofile_regex.search(f) if not devel_pkg and is_so and not link.endswith('.so'): self.output.add_info('W', pkg, 'devel-file-in-non-devel-package', f) res = man_base_regex.search(f) if res: man_basenames.add(res.group(1)) else: res = bin_regex.search(f) if res: exe = res.group(1) if '/' not in exe: bindir_exes.setdefault(exe, []) # absolute link r = absolute_regex.search(link) if r: if not is_so and link not in files and \ link not in req_names: is_exception = False for e in self.dangling_exceptions.values(): if e['path'].search(link): is_exception = e['name'] break if is_exception: if is_exception not in req_names: self.output.add_info('W', pkg, 'no-dependency-on', is_exception) else: self.output.add_info('W', pkg, 'dangling-symlink', f, link) linktop = r.group(1) r = absolute_regex.search(f) if r: filetop = r.group(1) if filetop == linktop or self.use_relative_symlinks: self.output.add_info('W', pkg, 'symlink-should-be-relative', f, link) # relative link else: if not is_so: abslink = '%s/%s' % (Path(f).parent, link) abslink = os.path.normpath(abslink) if abslink not in files and abslink not in req_names: is_exception = False for e in self.dangling_exceptions.values(): if e['path'].search(link): is_exception = e['name'] break if is_exception: if is_exception not in req_names: self.output.add_info('W', pkg, 'no-dependency-on', is_exception) else: self.output.add_info('W', pkg, 'dangling-relative-symlink', f, link) pathcomponents = f.split('/')[1:] r = points_regex.search(link) lastpop = None mylink = None while r: mylink = r.group(1) if len(pathcomponents) == 0: self.output.add_info('E', 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: # self.output.add_info('W', pkg, 'lengthy-symlink', f, link) # have we reached the root directory? if len(pathcomponents) == 0 and linktop != lastpop \ and not self.use_relative_symlinks: # relative link into other toplevel directory self.output.add_info('W', pkg, 'symlink-should-be-absolute', f, link) # check additional segments for mistakes like # `foo/../bar/' for linksegment in mylink.split('/'): if linksegment == '..': self.output.add_info('E', pkg, 'symlink-contains-up-and-down-segments', f, link) if f.startswith('/etc/cron.d/'): if stat.S_ISLNK(mode): self.output.add_info('E', pkg, 'symlink-crontab-file', f) if mode_is_exec: self.output.add_info('E', pkg, 'executable-crontab-file', f) if stat.S_IWGRP & mode or stat.S_IWOTH & mode: self.output.add_info('E', pkg, 'non-owner-writeable-only-crontab-file', f) if len(log_files) and not logrotate_file: self.output.add_info('W', pkg, 'log-files-without-logrotate', sorted(log_files)) if lib_package and lib_file and non_lib_file: self.output.add_info('E', pkg, 'outside-libdir-files', non_lib_file) if not self.use_debugsource and debuginfo_package and debuginfo_debugs and not debuginfo_srcs: self.output.add_info('E', pkg, 'debuginfo-without-sources') for exe, paths in bindir_exes.items(): if len(paths) > 1: self.output.add_info('W', pkg, 'duplicate-executable', exe, paths) if exe not in man_basenames: self.output.add_info('W', pkg, 'no-manual-page-for-binary', exe) def _check_manpage_compressed(self, pkg, fname): """ Check if the the manual page is compressed with the compression method stated in the rpmlint configuration (CompressExtension option). Print a warning if it's not compressed. """ if self.compress_ext and self.man_regex.search(fname): if not fname.endswith(self.compress_ext): self.output.add_info('W', pkg, 'manpage-not-compressed', self.compress_ext, fname) def _check_infopage_compressed(self, pkg, fname): """ Check if the the info page is compressed with the compression method stated in the rpmlint configuration (CompressExtension option). Print a warning if it's not compressed. """ if self.compress_ext and self.info_regex.search(fname) and \ not fname.endswith('/info/dir'): if not fname.endswith(self.compress_ext): self.output.add_info('W', pkg, 'infopage-not-compressed', self.compress_ext, fname) rpmlint-2.2.0+ds1/rpmlint/checks/I18NCheck.py000066400000000000000000000123211415540642600206050ustar00rootroot00000000000000############################################################################# # File : I18NCheck.py # Package : rpmlint # Author : Frederic Lepied # Created on : Mon Nov 22 20:02:56 1999 # Purpose : checks i18n bugs. ############################################################################# import re import rpm from rpmlint.__isocodes__ import COUNTRIES, LANGUAGES from rpmlint.checks.AbstractCheck import AbstractCheck # Associative array of invalid value => correct value INCORRECT_LOCALES = { 'in': 'id', 'in_ID': 'id_ID', 'iw': 'he', 'iw_IL': 'he_IL', 'gr': 'el', 'gr_GR': 'el_GR', 'cz': 'cs', 'cz_CZ': 'cs_CZ', 'lug': 'lg', # 'lug' is valid, but we standardize on 2 letter codes 'en_UK': 'en_GB'} package_regex = re.compile('-(' + '|'.join(LANGUAGES) + ')$') locale_regex = re.compile('^(/usr/share/locale/([^/]+))/') correct_subdir_regex = re.compile('^(([a-z][a-z]([a-z])?(_[A-Z][A-Z])?)([.@].*$)?)$') lc_messages_regex = re.compile('/usr/share/locale/([^/]+)/LC_MESSAGES/.*(mo|po)$') man_regex = re.compile('/usr(?:/share)?/man/([^/]+)/man[0-9n][^/]*/[^/]+$') # list of exceptions # # note: ISO-8859-9E is non standard, ISO-8859-{6,8} are of limited use # as locales (since all modern handling of bidi is based on utf-8 anyway), # so they should be removed once UTF-8 is deployed) EXCEPTION_DIRS = ( 'C', 'POSIX', 'CP1251', 'CP1255', 'CP1256', 'ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4', 'ISO-8859-5', 'ISO-8859-6', 'ISO-8859-7', 'ISO-8859-8', 'ISO-8859-9', 'ISO-8859-9E', 'ISO-8859-10', 'ISO-8859-13', 'ISO-8859-14', 'ISO-8859-15', 'KOI8-R', 'KOI8-U', 'UTF-8', 'default') def is_valid_lang(lang): # TODO: @Foo and charset handling lang = re.sub('[@.].*$', '', lang) if lang in LANGUAGES: return True ix = lang.find('_') if ix == -1: return False # TODO: don't accept all lang_COUNTRY combinations country = lang[ix + 1:] if country not in COUNTRIES: return False lang = lang[0:ix] if lang not in LANGUAGES: return False return True class I18NCheck(AbstractCheck): def check_binary(self, pkg): files = list(pkg.files.keys()) files.sort() locales = [] # list of locales for this packages webapp = False i18n_tags = pkg[rpm.RPMTAG_HEADERI18NTABLE] or () for i in i18n_tags: try: correct = INCORRECT_LOCALES[i] self.output.add_info('E', pkg, 'incorrect-i18n-tag-' + correct, i) except KeyError: pass # as some webapps have their files under /var/www/html, and # others in /usr/share or /usr/lib, the only reliable way # sofar to detect them is to look for an apache configuration file for f in files: if f.startswith('/etc/apache2/') or \ f.startswith('/etc/httpd/conf.d/'): webapp = True for f in files: res = locale_regex.search(f) if res: locale = res.group(2) # checks the same locale only once if locale not in locales: locales.append(locale) res2 = correct_subdir_regex.search(locale) if not res2: if locale not in EXCEPTION_DIRS: self.output.add_info('E', pkg, 'incorrect-locale-subdir', f) else: locale_name = res2.group(2) try: correct = INCORRECT_LOCALES[locale_name] self.output.add_info('E', pkg, 'incorrect-locale-' + correct, f) except KeyError: pass res = lc_messages_regex.search(f) subdir = None if res: subdir = res.group(1) if not is_valid_lang(subdir): self.output.add_info('E', pkg, 'invalid-lc-messages-dir', f) else: res = man_regex.search(f) if res: subdir = res.group(1) if is_valid_lang(subdir): subdir = None else: self.output.add_info('E', pkg, 'invalid-locale-man-dir', f) if f.endswith('.mo') or subdir: if pkg.files[f].lang == '' and not webapp: self.output.add_info('W', pkg, 'file-not-in-%lang', f) main_dir, main_lang = ('', '') for f in files: lang = pkg.files[f].lang if main_lang and lang == '' and is_prefix(main_dir + '/', f): self.output.add_info('E', pkg, 'subfile-not-in-%lang', f) if main_lang != lang: main_dir, main_lang = f, lang name = pkg.name res = package_regex.search(name) if res: locales = 'locales-' + res.group(1) if locales != name: if locales not in (x[0] for x in pkg.requires): self.output.add_info('E', pkg, 'no-dependency-on', locales) def is_prefix(p, s): return len(p) <= len(s) and p == s[:len(p)] rpmlint-2.2.0+ds1/rpmlint/checks/IconSizesCheck.py000066400000000000000000000017661415540642600220470ustar00rootroot00000000000000import re from rpmlint.checks.AbstractCheck import AbstractCheck class IconSizesCheck(AbstractCheck): file_size_regex = re.compile(r'/icons/[^/]+/(?P\d+)x(?P\d+)/') info_size_regex = re.compile(r'(?P\d+) x (?P\d+)') def check(self, pkg): if pkg.is_source: return for fname, pkgfile in pkg.files.items(): if '/animations/' in fname: continue res = self.file_size_regex.search(fname) res2 = self.info_size_regex.search(pkgfile.magic) if res and res2: sizes = (res.group('x'), res.group('y')) actualsizes = (res2.group('x'), res2.group('y')) if abs(int(sizes[0]) - int(actualsizes[0])) > 2 or \ abs(int(sizes[1]) - int(actualsizes[1])) > 2: self.output.add_info('E', pkg, 'wrong-icon-size', fname, 'expected:', 'x'.join(sizes), 'actual:', 'x'.join(actualsizes)) rpmlint-2.2.0+ds1/rpmlint/checks/InitScriptCheck.py000066400000000000000000000226261415540642600222270ustar00rootroot00000000000000############################################################################# # Project : Mandriva Linux # Module : rpmlint # File : InitScriptCheck.py # Author : Frederic Lepied # Created On : Fri Aug 25 09:26:37 2000 # Purpose : check init scripts (files in /etc/rc.d/init.d) ############################################################################# from pathlib import Path import re import rpm from rpmlint.checks.AbstractCheck import AbstractCheck from rpmlint.helpers import readlines chkconfig_content_regex = re.compile(r'^\s*#\s*chkconfig:\s*([-0-9]+)\s+[-0-9]+\s+[-0-9]+') subsys_regex = re.compile(r'/var/lock/subsys/([^/"\'\s;&|]+)', re.MULTILINE) chkconfig_regex = re.compile(r'^[^#]*(chkconfig|add-service|del-service)', re.MULTILINE) status_regex = re.compile(r'^[^#]*status', re.MULTILINE) reload_regex = re.compile(r'^[^#]*reload', re.MULTILINE) lsb_tags_regex = re.compile(r'^# ([\w-]+):\s*(.*?)\s*$') lsb_cont_regex = re.compile(r'^#(?:%s| )(.*?)\s*$' % '\t') LSB_KEYWORDS = ('Provides', 'Required-Start', 'Required-Stop', 'Should-Start', 'Should-Stop', 'Default-Start', 'Default-Stop', 'Short-Description', 'Description') RECOMMENDED_LSB_KEYWORDS = ('Provides', 'Required-Start', 'Required-Stop', 'Default-Stop', 'Short-Description') var_regex = re.compile(r'^(.*)\${?(\w+)}?(.*)$') def shell_var_value(var, script): assign_regex = re.compile(r'\b' + re.escape(var) + r'\s*=\s*(.+)\s*(#.*)*$', re.MULTILINE) res = assign_regex.search(script) if res: res2 = var_regex.search(res.group(1)) if res2: if res2.group(2) == var: # infinite loop return None return substitute_shell_vars(res.group(1), script) else: return None def substitute_shell_vars(val, script): res = var_regex.search(val) if res: value = shell_var_value(res.group(2), script) if not value: value = '' return res.group(1) + value + \ substitute_shell_vars(res.group(3), script) else: return val class InitScriptCheck(AbstractCheck): def __init__(self, config, output): super().__init__(config, output) self.use_deflevels = self.config.configuration['UseDefaultRunlevels'] self.use_subsys = self.config.configuration['UseVarLockSubsys'] def check_binary(self, pkg): initscript_list = [] for fname, pkgfile in pkg.files.items(): if not fname.startswith('/etc/init.d/') and \ not fname.startswith('/etc/rc.d/init.d/'): continue basename = Path(fname).name initscript_list.append(basename) if pkgfile.mode & 0o500 != 0o500: self.output.add_info('E', pkg, 'init-script-non-executable', fname) if '.' in basename: self.output.add_info('E', pkg, 'init-script-name-with-dot', fname) # check chkconfig call in %post and %preun postin = pkg[rpm.RPMTAG_POSTIN] or \ pkg.scriptprog(rpm.RPMTAG_POSTINPROG) if not postin: self.output.add_info('E', pkg, 'init-script-without-chkconfig-postin', fname) elif not chkconfig_regex.search(postin): self.output.add_info('E', pkg, 'postin-without-chkconfig', fname) preun = pkg[rpm.RPMTAG_PREUN] or \ pkg.scriptprog(rpm.RPMTAG_PREUNPROG) if not preun: self.output.add_info('E', pkg, 'init-script-without-chkconfig-preun', fname) elif not chkconfig_regex.search(preun): self.output.add_info('E', pkg, 'preun-without-chkconfig', fname) status_found = False reload_found = False chkconfig_content_found = False subsys_regex_found = False in_lsb_tag = False in_lsb_description = False lastline = '' lsb_tags = {} # check common error in file content content = None try: content = list(readlines(pkgfile.path)) except Exception as e: self.output.add_info('W', pkg, 'read-error', e) continue content_str = ''.join(content) for line in content: line = line[:-1] # chomp # TODO check if there is only one line like this if line.startswith('### BEGIN INIT INFO'): in_lsb_tag = True continue if line.endswith('### END INIT INFO'): in_lsb_tag = False for kw, vals in lsb_tags.items(): if len(vals) != 1: self.output.add_info('E', pkg, 'redundant-lsb-keyword', kw) for kw in RECOMMENDED_LSB_KEYWORDS: if kw not in lsb_tags: self.output.add_info('W', pkg, 'missing-lsb-keyword', '%s in %s' % (kw, fname)) if in_lsb_tag: # TODO maybe we do not have to handle this ? if lastline.endswith('\\'): line = lastline + line else: res = lsb_tags_regex.search(line) if not res: cres = lsb_cont_regex.search(line) if not (in_lsb_description and cres): in_lsb_description = False self.output.add_info('E', pkg, 'malformed-line-in-lsb-comment-block', line) else: lsb_tags['Description'][-1] += \ ' ' + cres.group(1) else: tag = res.group(1) if not tag.startswith('X-') and \ tag not in LSB_KEYWORDS: self.output.add_info('E', pkg, 'unknown-lsb-keyword', line) else: in_lsb_description = (tag == 'Description') if tag not in lsb_tags: lsb_tags[tag] = [] lsb_tags[tag].append(res.group(2)) lastline = line if not status_found and status_regex.search(line): status_found = True if not reload_found and reload_regex.search(line): reload_found = True res = chkconfig_content_regex.search(line) if res: chkconfig_content_found = True if self.use_deflevels: if res.group(1) == '-': self.output.add_info('W', pkg, 'no-default-runlevel', fname) elif res.group(1) != '-': self.output.add_info('W', pkg, 'service-default-enabled', fname) res = subsys_regex.search(line) if res: subsys_regex_found = True name = res.group(1) if self.use_subsys and name != basename: error = True if name[0] == '$': value = substitute_shell_vars(name, content_str) if value == basename: error = False else: i = name.find('}') if i != -1: name = name[0:i] error = name != basename if error and len(name): if name[0] == '$': self.output.add_info('W', pkg, 'incoherent-subsys', fname, name) else: self.output.add_info('E', pkg, 'incoherent-subsys', fname, name) if 'Default-Start' in lsb_tags: if ''.join(lsb_tags['Default-Start']): self.output.add_info('W', pkg, 'service-default-enabled', fname) if not status_found: self.output.add_info('E', pkg, 'no-status-entry', fname) if not reload_found: self.output.add_info('W', pkg, 'no-reload-entry', fname) if not chkconfig_content_found: self.output.add_info('E', pkg, 'no-chkconfig-line', fname) if not subsys_regex_found and self.use_subsys: self.output.add_info('E', pkg, 'subsys-not-used', fname) elif subsys_regex_found and not self.use_subsys: self.output.add_info('E', pkg, 'subsys-unsupported', fname) if len(initscript_list) == 1: pkgname = re.sub('-sysvinit$', '', pkg.name.lower()) goodnames = (pkgname, pkgname + 'd') if initscript_list[0] not in goodnames: self.output.add_info('W', pkg, 'incoherent-init-script-name', initscript_list[0], str(goodnames)) rpmlint-2.2.0+ds1/rpmlint/checks/LSBCheck.py000066400000000000000000000034271415540642600205550ustar00rootroot00000000000000import re import rpm from rpmlint.checks.AbstractCheck import AbstractCheck class LSBCheck(AbstractCheck): """ Validate that package name, version and release number are LSB compliant. The rules are the intersection of compatible NVRs between RPM v3 and DPKG for supporting portability across RPM and Debian systems through tools like alien. Note: It uses values gained from rpm (RPMTAGs) not parsed from .rpm filename. """ name_regex = re.compile('^[a-z0-9.+-]+$') version_regex = re.compile('^[a-zA-Z0-9.+]+$') def check(self, pkg): self._check_lsb_name(pkg) self._check_lsb_version(pkg) self._check_lsb_release(pkg) def _check_lsb_name(self, pkg): """ Check if the package name is LSB compliant (only lowercase letters, numbers, '.', '+' or '-' characters). """ name = pkg.name if name and not self.name_regex.search(name): self.output.add_info('E', pkg, 'non-lsb-compliant-package-name', pkg.name) def _check_lsb_version(self, pkg): """ Check if the package version number is LSB compliant (only alphanumeric symbols, '.' or '+' characters). """ version = pkg[rpm.RPMTAG_VERSION] if version and not self.version_regex.search(version): self.output.add_info('E', pkg, 'non-lsb-compliant-version', pkg[rpm.RPMTAG_VERSION]) def _check_lsb_release(self, pkg): """ Check if the package release number is LSB compliant (only alphanumeric symbols, '.' or '+' characters). """ release = pkg[rpm.RPMTAG_RELEASE] if release and not self.version_regex.search(release): self.output.add_info('E', pkg, 'non-lsb-compliant-release', pkg[rpm.RPMTAG_RELEASE]) rpmlint-2.2.0+ds1/rpmlint/checks/LibraryDependencyCheck.py000066400000000000000000000041331415540642600235330ustar00rootroot00000000000000from pathlib import Path import stat from rpmlint.checks import FilesCheck from rpmlint.checks.AbstractCheck import AbstractCheck from rpmlint.pkg import FakePkg class LibraryDependencyCheck(AbstractCheck): def __init__(self, config, output): super().__init__(config, output) self.package_requires = {} self.package_so_symlinks = {} self.package_so_files = {} self.package_arch_mapping = {} def check_binary(self, pkg): if pkg.is_source: return is_devel = FilesCheck.devel_regex.search(pkg.name) if is_devel: self._process_devel_package(pkg, is_devel) else: self._process_nondevel_package(pkg) def _process_devel_package(self, pkg, is_devel): self.package_requires[pkg.name] = [req[0] for req in pkg.requires + pkg.prereq] self.package_so_symlinks[pkg.name] = [] self.package_arch_mapping[pkg.name] = pkg.arch for pkgfile in pkg.files.values(): if stat.S_ISLNK(pkgfile.mode) and pkgfile.name.endswith('.so'): link = Path(pkgfile.name).parent / pkgfile.linkto self.package_so_symlinks[pkg.name].append(str(link)) def _process_nondevel_package(self, pkg): for pkgfile in pkg.files.values(): if '.so' in pkgfile.name: self.package_so_files[pkgfile.name] = pkg.name def after_checks(self): for pkgname, so_symlinks in self.package_so_symlinks.items(): for link in so_symlinks: with FakePkg(pkgname) as pkg: pkg.arch = self.package_arch_mapping[pkgname] if link not in self.package_so_files: self.output.add_info('E', pkg, 'no-library-dependency-for', link) break else: definition = self.package_so_files[link] if definition not in self.package_requires[pkgname]: self.output.add_info('E', pkg, 'no-library-dependency-on', definition, link) break rpmlint-2.2.0+ds1/rpmlint/checks/LogrotateCheck.py000066400000000000000000000052451415540642600220750ustar00rootroot00000000000000import os from rpmlint.checks.AbstractCheck import AbstractCheck class LogrotateCheck(AbstractCheck): def check(self, pkg): if pkg.is_source: return files = pkg.files dirs = {} for f in files: if f in pkg.ghost_files: continue if f.startswith('/etc/logrotate.d/'): try: for n, o in self.parselogrotateconf(pkg.dirName(), f).items(): if n in dirs and dirs[n] != o: self.output.add_info('E', pkg, 'logrotate-duplicate', n) else: dirs[n] = o except Exception as e: self.output.add_info('E', pkg, 'logrotate-exception', f, str(e)) for d in sorted(dirs.keys()): if d not in files: self.output.add_info('E', pkg, 'logrotate-log-dir-not-packaged', d) continue mode = files[d].mode & 0o777 if files[d].user != 'root' and (dirs[d] is None or dirs[d][0] != files[d].user): self.output.add_info('E', pkg, 'logrotate-user-writable-log-dir', '%s %s:%s %04o' % (d, files[d].user, files[d].group, mode)) elif files[d].group != 'root' and mode & 0o20 and (dirs[d] is None or dirs[d][1] != files[d].group): self.output.add_info('E', pkg, 'logrotate-user-writable-log-dir', '%s %s:%s %04o' % (d, files[d].user, files[d].group, mode)) # extremely primitive logrotate parser def parselogrotateconf(self, root, f): dirs = {} with open('/'.join((root, f))) as fd: currentdirs = [] for line in fd.readlines(): line = line.strip() if line.startswith('#'): continue if not currentdirs: if line.endswith('{'): for logfile in line.split(' '): logfile = logfile.strip() if not logfile or logfile == '{': continue dn = os.path.dirname(logfile) if dn not in dirs: currentdirs.append(dn) dirs[dn] = None else: if line.endswith('}'): currentdirs = [] elif line.startswith('su '): a = line.split(' ') for dn in currentdirs: dirs[dn] = (a[1], a[2]) return dirs rpmlint-2.2.0+ds1/rpmlint/checks/MenuCheck.py000066400000000000000000000265741415540642600210510ustar00rootroot00000000000000############################################################################# # Project : Mandriva Linux # Module : rpmlint # File : MenuCheck.py # Author : Frederic Lepied # Created On : Mon Mar 20 07:43:37 2000 ############################################################################# import re import stat import subprocess import rpm from rpmlint.checks.AbstractCheck import AbstractCheck from rpmlint.helpers import ENGLISH_ENVIROMENT menu_file_regex = re.compile(r'^/usr/lib/menu/([^/]+)$') old_menu_file_regex = re.compile(r'^/usr/share/(gnome/apps|applnk)/([^/]+)$') package_regex = re.compile(r'\?package\((.*)\):') needs_regex = re.compile(r'needs=(\"([^\"]+)\"|([^ %s\"]+))' % '\t') section_regex = re.compile(r'section=(\"([^\"]+)\"|([^ %s\"]+))' % '\t') title_regex = re.compile(r'[\"\s]title=(\"([^\"]+)\"|([^ %s\"]+))' % '\t') longtitle_regex = re.compile(r'longtitle=(\"([^\"]+)\"|([^ %s\"]+))' % '\t') command_regex = re.compile(r'command=(?:\"([^\"]+)\"|([^ %s\"]+))' % '\t') icon_regex = re.compile(r'icon=\"?([^\" ]+)') update_menus_regex = re.compile(r'^[^#]*update-menus', re.MULTILINE) xpm_ext_regex = re.compile(r'/usr/share/icons/(mini/|large/).*\.xpm$') version_regex = re.compile(r'([0-9.][0-9.]+)($|\s)') xdg_migrated_regex = re.compile(r'xdg=\"?([^\" ]+)') class MenuCheck(AbstractCheck): def __init__(self, config, output): super().__init__(config, output) self.valid_sections = self.config.configuration['ValidMenuSections'] self.standard_needs = self.config.configuration['ExtraMenuNeeds'] self.icon_paths = self.config.configuration['IconPath'] self.launchers = self.config.configuration['MenuLaunchers'] self.icon_ext_regex = re.compile(self.config.configuration['IconFilename']) # compile regexps for value in self.launchers.values(): value['regexp'] = re.compile(value['regexp']) def check_binary(self, pkg): files = pkg.files menus = [] for fname, pkgfile in files.items(): # Check menu files res = menu_file_regex.search(fname) mode = pkgfile.mode if res: basename = res.group(1) if not stat.S_ISREG(mode): self.output.add_info('E', pkg, 'non-file-in-menu-dir', fname) else: if basename != pkg.name: self.output.add_info('W', pkg, 'non-coherent-menu-filename', fname) if mode & 0o444 != 0o444: self.output.add_info('E', pkg, 'non-readable-menu-file', fname) if mode & 0o111: self.output.add_info('E', pkg, 'executable-menu-file', fname) menus.append(fname) else: # Check old menus from KDE and GNOME res = old_menu_file_regex.search(fname) if res: if stat.S_ISREG(mode): self.output.add_info('E', pkg, 'old-menu-entry', fname) else: # Check non transparent xpm files res = xpm_ext_regex.search(fname) if res: if stat.S_ISREG(mode) and not pkg.grep('None",', fname): self.output.add_info('W', pkg, 'non-transparent-xpm', fname) if fname.startswith('/usr/lib64/menu'): self.output.add_info('E', pkg, 'menu-in-wrong-dir', fname) if menus: postin = pkg[rpm.RPMTAG_POSTIN] or \ pkg.scriptprog(rpm.RPMTAG_POSTINPROG) if not postin: self.output.add_info('E', pkg, 'menu-without-postin') elif not update_menus_regex.search(postin): self.output.add_info('E', pkg, 'postin-without-update-menus') postun = pkg[rpm.RPMTAG_POSTUN] or \ pkg.scriptprog(rpm.RPMTAG_POSTUNPROG) if not postun: self.output.add_info('E', pkg, 'menu-without-postun') elif not update_menus_regex.search(postun): self.output.add_info('E', pkg, 'postun-without-update-menus') directory = pkg.dirName() for f in menus: # remove comments and handle cpp continuation lines text = subprocess.run(('/lib/cpp', directory + f), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=ENGLISH_ENVIROMENT).stdout.decode() if text.endswith('\n'): text = text[:-1] for line in text.splitlines(): if not line.startswith('?'): continue res = package_regex.search(line) if res: package = res.group(1) if package != pkg.name: self.output.add_info('W', pkg, 'incoherent-package-value-in-menu', package, f) else: self.output.add_info('I', pkg, 'unable-to-parse-menu-entry', line) command = True res = command_regex.search(line) if res: command_line = (res.group(1) or res.group(2)).split() command = command_line[0] for launcher in self.launchers.values(): if not launcher['regexp'].search(command): continue found = False if launcher['binaries']: found = '/bin/' + command_line[0] in files or \ '/usr/bin/' + command_line[0] in files or \ '/usr/X11R6/bin/' + command_line[0] \ in files if not found: for i in launcher['binaries']: if i in pkg.req_names: found = True break if not found: self.output.add_info('E', pkg, 'use-of-launcher-in-menu-but-no-requires-on', launcher['binaries'][0]) command = command_line[1] break if command[0] == '/': if command not in files: self.output.add_info('W', pkg, 'menu-command-not-in-package', command) elif not ('/bin/' + command in files or '/usr/bin/' + command in files or '/usr/X11R6/bin/' + command in files): self.output.add_info('W', pkg, 'menu-command-not-in-package', command) else: self.output.add_info('W', pkg, 'missing-menu-command') command = False res = longtitle_regex.search(line) if res: grp = res.groups() title = grp[1] or grp[2] if title[0] != title[0].upper(): self.output.add_info('W', pkg, 'menu-longtitle-not-capitalized', title) res = version_regex.search(title) if res: self.output.add_info('W', pkg, 'version-in-menu-longtitle', title) else: self.output.add_info('E', pkg, 'no-longtitle-in-menu', f) title = None res = title_regex.search(line) if res: grp = res.groups() title = grp[1] or grp[2] if title[0] != title[0].upper(): self.output.add_info('W', pkg, 'menu-title-not-capitalized', title) res = version_regex.search(title) if res: self.output.add_info('W', pkg, 'version-in-menu-title', title) if '/' in title: self.output.add_info('E', pkg, 'invalid-title', title) else: self.output.add_info('E', pkg, 'no-title-in-menu', f) title = None res = needs_regex.search(line) if res: grp = res.groups() needs = (grp[1] or grp[2]).lower() if needs in ('x11', 'text', 'wm'): res = section_regex.search(line) if res: grp = res.groups() section = grp[1] or grp[2] # don't warn entries for sections if command and section not in self.valid_sections: self.output.add_info('E', pkg, 'invalid-menu-section', section, f) else: self.output.add_info('I', pkg, 'unable-to-parse-menu-section', line) elif needs not in self.standard_needs: self.output.add_info('I', pkg, 'strange-needs', needs, f) else: self.output.add_info('I', pkg, 'unable-to-parse-menu-needs', line) res = icon_regex.search(line) if res: icon = res.group(1) if not self.icon_ext_regex.search(icon): self.output.add_info('W', pkg, 'invalid-menu-icon-type', icon) if icon[0] == '/' and needs == 'x11': self.output.add_info('W', pkg, 'hardcoded-path-in-menu-icon', icon) else: for value in self.icon_paths.values(): if (value['path'] + icon) not in files: self.output.add_info('E', pkg, value['type'] + '-icon-not-in-package', icon, f) else: self.output.add_info('W', pkg, 'no-icon-in-menu', title) res = xdg_migrated_regex.search(line) if res: if not res.group(1).lower() == 'true': self.output.add_info('E', pkg, 'non-xdg-migrated-menu') else: self.output.add_info('E', pkg, 'non-xdg-migrated-menu') rpmlint-2.2.0+ds1/rpmlint/checks/MenuXDGCheck.py000066400000000000000000000101561415540642600214010ustar00rootroot00000000000000 # # check xdg file format violation # # http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html # import codecs import configparser as cfgparser from pathlib import Path import subprocess from rpmlint.checks.AbstractCheck import AbstractFilesCheck from rpmlint.helpers import ENGLISH_ENVIROMENT STANDARD_BIN_DIRS = ('/bin', '/sbin', '/usr/bin', '/usr/sbin') class MenuXDGCheck(AbstractFilesCheck): """ Check whether MenuXDG files installed by a package are valid. """ def __init__(self, config, output): # desktop file need to be in $XDG_DATA_DIRS # $ echo $XDG_DATA_DIRS/applications # /var/lib/menu-xdg:/usr/share super().__init__(config, output, r'/usr/share/applications/.*\.desktop$') def parse_desktop_file(self, pkg, root, f, filename): """ Check the structure of a desktop file. """ cfp = cfgparser.RawConfigParser() try: with codecs.open(f, encoding='utf-8') as inputf: cfp.read_file(inputf, filename) except cfgparser.Error as e: self._handle_parser_error(pkg, filename, e) except UnicodeDecodeError as e: self.output.add_info('E', pkg, 'non-utf8-desktopfile', filename, f'Unicode error: {e}') else: self._has_binary(pkg, root, cfp, filename) def check_file(self, pkg, filename): root = pkg.dirName() f = root + filename try: command = subprocess.run(('desktop-file-validate', f), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=ENGLISH_ENVIROMENT) text = command.stdout.decode() if command.returncode: error_printed = False for line in text.splitlines(): if 'error: ' in line: self.output.add_info('E', pkg, 'invalid-desktopfile', filename, line.split('error: ')[1]) error_printed = True if not error_printed: self.output.add_info('E', pkg, 'invalid-desktopfile', filename) self.parse_desktop_file(pkg, root, f, filename) except UnicodeDecodeError as e: self.output.add_info('E', pkg, 'non-utf8-desktopfile', filename, f'Unicode error: {e}') def _handle_parser_error(self, pkg, filename, e): """ Determine what to do with a caught configparser error. """ # I would love to use switch, however, each warning is printed differently if (isinstance(e, cfgparser.MissingSectionHeaderError)): self.output.add_info('E', pkg, 'desktopfile-missing-header', filename) elif (isinstance(e, cfgparser.DuplicateSectionError)): self.output.add_info('E', pkg, 'desktopfile-duplicate-section', filename, '[{e.section}]') elif (isinstance(e, cfgparser.DuplicateOptionError)): self.output.add_info('E', pkg, 'desktopfile-duplicate-option', filename, '[{e.section}]/{e.option}') else: self.output.add_info('E', pkg, 'invalid-desktopfile', filename, e.message.partition(':')[0]) def _has_binary(self, pkg, root, cfp, filename): """ Check whether there is a binary assigned to the desktop file. Needs configparser instance, it is assumed to be called in parse_desktop_file. """ binary = None if cfp.has_option('Desktop Entry', 'Exec'): binary = cfp.get('Desktop Entry', 'Exec').partition(' ')[0] # If there is no binary mentioned it is OK if not binary: return if binary.startswith('/'): if (Path(root + binary).exists()): return else: for i in STANDARD_BIN_DIRS: if Path(root + i + '/' + binary).exists(): # no need to check if the binary is +x, rpmlint does it # in another place return self.output.add_info('W', pkg, 'desktopfile-without-binary', filename, binary) rpmlint-2.2.0+ds1/rpmlint/checks/MixedOwnershipCheck.py000066400000000000000000000020611415540642600230730ustar00rootroot00000000000000from rpmlint.checks.AbstractCheck import AbstractCheck class MixedOwnershipCheck(AbstractCheck): def check(self, pkg): """ Check for mixed permissions in the directory path. If folder is owned by i.e. nobody then the files there shouldn't be owned by other user either, as then nobody could replace the files and inject anything. """ if pkg.is_source: return for path, info in pkg.files.items(): parent = path.rpartition('/')[0] # In case parent folder is not part of this RPM we can't verify it if parent not in pkg.files: continue parent_owner = pkg.files[parent].user # root user is trusted if parent_owner in ('root', '0'): continue if info.user != parent_owner: message = f'Path "{path}" owned by "{info.user}" is stored in directory owned by "{parent_owner}"' self.output.add_info('E', pkg, 'file-parent-ownership-mismatch', message) rpmlint-2.2.0+ds1/rpmlint/checks/PAMModulesCheck.py000066400000000000000000000013571415540642600221030ustar00rootroot00000000000000import re from rpmlint.checks.AbstractCheck import AbstractCheck class PAMModulesCheck(AbstractCheck): pam_module_re = re.compile(r'^(?:/usr)?/lib(?:64)?/security/([^/]+\.so)$') def __init__(self, config, output): super().__init__(config, output) self.pam_authorized_modules = config.configuration['PAMAuthorizedModules'] def check(self, pkg): if pkg.is_source: return for f in pkg.files: if f in pkg.ghost_files: continue m = self.pam_module_re.match(f) if m: bn = m.groups()[0] if bn not in self.pam_authorized_modules: self.output.add_info('E', pkg, 'pam-unauthorized-module', bn) rpmlint-2.2.0+ds1/rpmlint/checks/PkgConfigCheck.py000066400000000000000000000047001415540642600217770ustar00rootroot00000000000000import re import stat from rpmlint.checks.AbstractCheck import AbstractFilesCheck class PkgConfigCheck(AbstractFilesCheck): """ Validate that .pc files are correct. """ suspicious_dir = re.compile(r'[=:](?:/usr/src/\w+/BUILD|/var/tmp|/tmp|/home)') def __init__(self, config, output): super().__init__(config, output, r'.*/pkgconfig/.*\.pc$') def check(self, pkg): # check for references to /lib when in lib64 mode and vice versa if pkg.arch in ('x86_64', 'ppc64', 's390x', 'aarch64'): self.wronglib_dir = re.compile(r'-L/usr/lib\b') else: self.wronglib_dir = re.compile(r'-L/usr/lib64\b') AbstractFilesCheck.check(self, pkg) def check_file(self, pkg, filename): if pkg.is_source or not stat.S_ISREG(pkg.files[filename].mode): return try: with open(pkg.dirName() + '/' + filename, 'r', encoding='utf-8') as pc_file: for line in pc_file: self._check_invalid_pkgconfig_file(pkg, filename, line) self._check_invalid_libs_dir(pkg, filename, line) self._check_double_slash(pkg, filename, line) except Exception as e: self.output.add_info('E', pkg, 'pkgconfig-exception', filename, str(e)) def _check_invalid_pkgconfig_file(self, pkg, filename, line): """ Check that .pc file is valid (it runs various checks). E.g. it doesn't contain traces of $RPM_BUILD_ROOT or $RPM_BUILD_DIR, unreplaced macros or invalid paths. """ if self.suspicious_dir.search(line): self.output.add_info('E', pkg, 'invalid-pkgconfig-file', filename) def _check_invalid_libs_dir(self, pkg, filename, line): """ Check that .pc file contains correct libs dir based on the build target (32-bit, 64-bit). That means: -L/usr/lib or -L/lib for 32-bit, -L/usr/lib64 or -L/lib64 for 64-bit """ if line.startswith('Libs:') and self.wronglib_dir.search(line): self.output.add_info('E', pkg, 'pkgconfig-invalid-libs-dir', filename, line.rstrip()) def _check_double_slash(self, pkg, filename, line): """ Check that .pc file doesn't contain a path with a double slash ('//') """ if '//' in line and '://' not in line: self.output.add_info('E', pkg, 'double-slash-in-pkgconfig-path', filename, line.rstrip()) rpmlint-2.2.0+ds1/rpmlint/checks/PostCheck.py000066400000000000000000000222511415540642600210560ustar00rootroot00000000000000############################################################################# # Project : Mandriva Linux # Module : rpmlint # File : PostCheck.py # Author : Frederic Lepied # Created On : Wed Jul 5 13:30:17 2000 # Purpose : Check post/pre scripts ############################################################################# import os import re import subprocess import tempfile import rpm from rpmlint import pkg as Pkg from rpmlint.checks.AbstractCheck import AbstractCheck from rpmlint.helpers import byte_to_string, ENGLISH_ENVIROMENT # shells that grok the -n switch for debugging syntaxcheck_shells = ('/bin/sh', '/bin/bash') percent_regex = re.compile(r'^[^#]*%{?\w{3,}', re.MULTILINE) bracket_regex = re.compile(r'^[^#]*if\s+[^ :\]]\]', re.MULTILINE) home_regex = re.compile(r'[^a-zA-Z]+~/|\${?HOME(\W|$)', re.MULTILINE) dangerous_command_regex = re.compile(r'(^|[;\|`]|&&|$\()\s*(?:\S*/s?bin/)?(cp|mv|ln|tar|rpm|chmod|chown|rm|cpio|install|perl|userdel|groupdel)\s', re.MULTILINE) selinux_regex = re.compile(r'(^|[;\|`]|&&|$\()\s*(?:\S*/s?bin/)?(chcon|runcon)\s', re.MULTILINE) single_command_regex = re.compile(r'^[ %s]*([^ %s]+)[ %s]*$' % (('\n',) * 3)) tmp_regex = re.compile(r'^[^#]*\s(/var)?/tmp', re.MULTILINE) menu_regex = re.compile(r'^/usr/lib/menu/|^/etc/menu-methods/|^/usr/share/applications/') bogus_var_regex = re.compile(r'(\${?RPM_BUILD_(ROOT|DIR)}?)') prereq_assoc = ( # ['chkconfig', ('chkconfig', '/sbin/chkconfig')], ['chkfontpath', ('chkfontpath', '/usr/sbin/chkfontpath')], ['rpm-helper', ('rpm-helper',)], ) for p in prereq_assoc: p[0] = re.compile(r'^[^#]+' + p[0], re.MULTILINE) # pychecker fix del p def incorrect_shell_script(prog, shellscript): return check_syntax_script(prog, '-n', shellscript) def incorrect_perl_script(prog, perlscript): return check_syntax_script(prog, '-wc', perlscript) def check_syntax_script(prog, commandline, script): if not script: return False if isinstance(script, str): script = script.encode('utf-8') # TODO: test that 'prog' is available/executable tmpfd, tmpname = tempfile.mkstemp(prefix='rpmlint.') tmpfile = os.fdopen(tmpfd, 'wb') try: tmpfile.write(script) tmpfile.close() ret = subprocess.run((prog, commandline, tmpname), env=ENGLISH_ENVIROMENT) finally: tmpfile.close() os.remove(tmpname) return ret.returncode class PostCheck(AbstractCheck): def __init__(self, config, output): super().__init__(config, output) self.valid_shells = config.configuration['ValidShells'] self.empty_shells = config.configuration['ValidEmptyShells'] post_details_dict = { 'postin-without-ghost-file-creation': """A file tagged as ghost is not created during %prein nor during %postin.""", } for scriptlet in map(lambda x: '%' + x, Pkg.RPM_SCRIPTLETS): post_details_dict.update({ 'one-line-command-in-%s' % scriptlet: """You should use %s -p instead of using: %s It will avoid the fork of a shell interpreter to execute your command as well as allows rpm to automatically mark the dependency on your command for the execution of the scriptlet.""" % (scriptlet, scriptlet), 'percent-in-%s' % scriptlet: """The %s scriptlet contains a '%%' in a context which might indicate it being fallout from an rpm macro/variable which was not expanded during build. Investigate whether this is the case and fix if appropriate.""" % scriptlet, 'spurious-bracket-in-%s' % scriptlet: """The %s scriptlet contains an 'if []' construct without a space before the ']'.""" % scriptlet, 'forbidden-selinux-command-in-%s' % scriptlet: """A command which requires intimate knowledge about a specific SELinux policy type was found in the scriptlet. These types are subject to change on a policy version upgrade. Use the restorecon command which queries the currently loaded policy for the correct type instead.""", 'non-empty-%s' % scriptlet: """Scriptlets for the interpreter mentioned in the message should be empty. One common case where they are unintentionally not is when the specfile contains comments after the scriptlet and before the next section. Review and clean up the scriptlet contents if appropriate.""", }) self.output.error_details.update(post_details_dict) def check_binary(self, pkg): prereq = [x[0] for x in pkg.prereq] for tag in Pkg.SCRIPT_TAGS: script = pkg[tag[0]] if not isinstance(script, list): prog = pkg.scriptprog(tag[1]) if prog: prog = prog.split()[0] self.check_aux(pkg, prog, pkg.header[tag[0]], tag[2], prereq) else: prog = pkg[tag[1]] for idx in range(0, len(prog)): self.check_aux( pkg, prog[idx], pkg.header[tag[0]][idx], tag[2], prereq) ghost_files = pkg.ghost_files if ghost_files: postin = pkg[rpm.RPMTAG_POSTIN] prein = pkg[rpm.RPMTAG_PREIN] for f in ghost_files: if f in pkg.missingok_files: continue if not postin and not prein: self.output.add_info('W', pkg, 'ghost-files-without-postin') if (not postin or f not in postin) and \ (not prein or f not in prein): self.output.add_info('W', pkg, 'postin-without-ghost-file-creation', f) def check_aux(self, pkg, prog, script, tag, prereq): files = pkg.files if script: script_str = byte_to_string(script) if prog: if prog not in self.valid_shells: self.output.add_info('E', pkg, 'invalid-shell-in-' + tag, prog) if prog in self.empty_shells: self.output.add_info('E', pkg, 'non-empty-' + tag, prog) if prog in syntaxcheck_shells or prog == '/usr/bin/perl': if percent_regex.search(script_str): self.output.add_info('W', pkg, 'percent-in-' + tag) if bracket_regex.search(script_str): self.output.add_info('W', pkg, 'spurious-bracket-in-' + tag) res = dangerous_command_regex.search(script_str) if res: self.output.add_info('W', pkg, 'dangerous-command-in-' + tag, res.group(2)) res = selinux_regex.search(script_str) if res: self.output.add_info('E', pkg, 'forbidden-selinux-command-in-' + tag, res.group(2)) if 'update-menus' in script_str: menu_error = True for f in files: if menu_regex.search(f): menu_error = False break if menu_error: self.output.add_info('E', pkg, 'update-menus-without-menu-file-in-' + tag) if tmp_regex.search(script_str): self.output.add_info('E', pkg, 'use-tmp-in-' + tag) for c in prereq_assoc: if c[0].search(script_str): found = False for p in c[1]: if p in prereq or p in files: found = True break if not found: self.output.add_info('E', pkg, 'no-prereq-on', c[1][0]) if prog in syntaxcheck_shells: if incorrect_shell_script(prog, script): self.output.add_info('E', pkg, 'shell-syntax-error-in-' + tag) if home_regex.search(script_str): self.output.add_info('E', pkg, 'use-of-home-in-' + tag) res = bogus_var_regex.search(script_str) if res: self.output.add_info('W', pkg, 'bogus-variable-use-in-' + tag, res.group(1)) if prog == '/usr/bin/perl': if incorrect_perl_script(prog, script): self.output.add_info('E', pkg, 'perl-syntax-error-in-' + tag) elif prog.endswith('sh'): res = single_command_regex.search(script_str) if res: self.output.add_info('W', pkg, 'one-line-command-in-' + tag, res.group(1)) elif prog not in self.empty_shells and prog in self.valid_shells: self.output.add_info('W', pkg, 'empty-' + tag) rpmlint-2.2.0+ds1/rpmlint/checks/SharedLibraryPolicyCheck.py000066400000000000000000000131111415540642600240370ustar00rootroot00000000000000from pathlib import Path import re import stat import rpm from rpmlint.checks.AbstractCheck import AbstractCheck from rpmlint.pkg import formatRequire from rpmlint.readelfparser import ReadelfParser class SharedLibraryPolicyCheck(AbstractCheck): """ Verify shared library packaging policy rules This package validates the shared libraries naming scheme based on the Debian/openSUSE shared libraries policy: https://en.opensuse.org/openSUSE:Shared_library_packaging_policy https://www.debian.org/doc/debian-policy/ch-sharedlibs.html """ def __init__(self, config, output): super().__init__(config, output) self.re_soname_strongly_versioned = re.compile(r'-[\d\.]+\.so$') # the pkgname is based on soname if ending with number; special option is flavor build self.re_soname_pkg = re.compile(r'^lib\S+(\d+(-(32|64)bit)?)$') self.re_so_files = re.compile(r'\S+.so((.(\d+))+)?$') def _check_missing_policy_lib(self, pkg): # check the pkg has any libname libfiles = [] for fname in pkg.files.keys(): if not self.re_so_files.match(fname): continue libfiles.append(fname) # if we didn't find any library files then we # don't need to check anything if not libfiles: # verify if name does not match the slpp and if we still don't have any lib then error out if self.re_soname_pkg.match(pkg.name): self.output.add_info('E', pkg, 'shlib-policy-missing-lib') def check(self, pkg): if pkg.is_source: return # Consider only non-development, non-language library packages if (not pkg.name.startswith('lib') or pkg.name.endswith('-devel') or pkg.name.endswith('-lang')): return self._check_missing_policy_lib(pkg) # the soname validation matching the name is done # already in BinaryCheck._check_shared_library # Search for shared libraries in this package libs = set() libs_needed = set() libs_to_dir = {} reqlibs = set() pkg_requires = {x.name.split('(')[0] for x in pkg.requires} for filename, pkgfile in pkg.files.items(): path = Path(filename) if '.so.' in filename or filename.endswith('.so'): if stat.S_ISREG(pkg.files[filename].mode) and pkgfile.magic.startswith('ELF '): readelf_parser = ReadelfParser(pkgfile.path, filename) failed_reason = readelf_parser.parsing_failed_reason() if failed_reason: self.output.add_info('E', pkg, 'readelf-failed', filename, failed_reason) return dyn_section = readelf_parser.dynamic_section_info libs_needed = libs_needed.union(dyn_section.needed) if dyn_section.soname: lib_dir = str(path.parent) libs.add(dyn_section.soname) libs_to_dir[dyn_section.soname] = lib_dir if dyn_section.soname in pkg_requires: # But not if the library is used by the pkg itself # This avoids program packages with their own # private lib # FIXME: we'd need to check if somebody else links # to this lib reqlibs.add(dyn_section.soname) if not libs.difference(reqlibs): return if pkg.name[-1].isdigit(): # ignore libs in a versioned non_std_dir for lib in libs.copy(): lib_dir = libs_to_dir[lib] for lib_part in lib_dir.split('/'): if not lib_part: continue if lib_part[-1].isdigit() and not lib_part.endswith('lib64'): libs.remove(lib) break # Check for non-versioned libs in a std lib package for lib in libs.copy(): if (not (lib[-1].isdigit() or self.re_soname_strongly_versioned.search(lib))): self.output.add_info('W', pkg, 'shlib-unversioned-lib', lib) # Verify shared lib policy package doesn't have hard dependency on non-lib packages for dep in pkg.requires: if dep[0].startswith('rpmlib(') or dep[0].startswith('config('): continue if (dep[1] & (rpm.RPMSENSE_GREATER | rpm.RPMSENSE_EQUAL)) == rpm.RPMSENSE_EQUAL: self.output.add_info('W', pkg, 'shlib-fixed-dependency', formatRequire(dep[0], dep[1], dep[2])) # Verify non-lib stuff does not add dependencies if libs: for dep in pkg_requires: if '.so.' in dep and dep not in libs and dep not in libs_needed: self.output.add_info('E', pkg, 'shlib-policy-excessive-dependency', dep) # FIXME: implement (#567) # Check if the files/folders are unversioned in the library package. # In general you can't co-install the soname packages if they all provide some datafiles # or configuration files. # When testing one of examples is libsemanage1: # /etc/selinux # /etc/selinux/semanage.conf # /usr/lib64/libsemanage.so.1 # The above would be fine if the semanage.conf would be update-alternatived, or suffixed # but if someone introduces libsemanage2 they can't be installed both at once. rpmlint-2.2.0+ds1/rpmlint/checks/SignatureCheck.py000066400000000000000000000044051415540642600220730ustar00rootroot00000000000000import re from rpmlint.checks.AbstractCheck import AbstractCheck from rpmlint.helpers import print_warning class SignatureCheck(AbstractCheck): """ Checks for PGP signature in the package. It checks if the signature is present, known (imported in RPM DB) and valid. It uses 'rpm -Kv' command than returns detailed information about the package digests and signature. """ any_sig_regex = re.compile(r'[Ss]ignature, key ID') nokey_sig_regex = re.compile(r'[Ss]ignature, key ID ([\w\d]*): NOKEY') invalid_sig_regex = re.compile(r'invalid OpenPGP signature') def check(self, pkg): retcode, output = pkg.checkSignature() # Skip all signature checks if checkSignature output is empty if output is None: print_warning(f'No output from checkSignature() for ' f'{pkg.filename}. Skipping signature checks.') return self._check_no_signature(pkg, retcode, output) self._check_unknown_key(pkg, retcode, output) self._check_invalid_signature(pkg, retcode, output) def _check_no_signature(self, pkg, retcode, output): """ Check if the package contains a signature. Print an error if there is no signature present. That means that there is no mention about any signature in the 'rpm -Kv' output. """ if retcode == 0 and not SignatureCheck.any_sig_regex.search(output): self.output.add_info('E', pkg, 'no-signature') def _check_unknown_key(self, pkg, retcode, output): """ Check if the public key is imported in the RPM database. Print an error if it's not imported and signature is therefore unknown. """ if retcode == 1: nokey = SignatureCheck.nokey_sig_regex.search(output) if nokey and not SignatureCheck.invalid_sig_regex.search(output): self.output.add_info('E', pkg, 'unknown-key', nokey.group(1)) def _check_invalid_signature(self, pkg, retcode, output): """ Check if the signature is valid. Print an error if the signature is corrupted. """ if retcode == 1 and SignatureCheck.invalid_sig_regex.search(output): self.output.add_info('E', pkg, 'invalid-signature') rpmlint-2.2.0+ds1/rpmlint/checks/SourceCheck.py000066400000000000000000000056351415540642600214000ustar00rootroot00000000000000import re from rpmlint.checks.AbstractCheck import AbstractCheck class SourceCheck(AbstractCheck): """ Validate files in a source package. """ source_regex = re.compile(r'\.(tar|tgz)$') compressed_fileext_magic = { 'xz': 'XZ compressed', 'gz': 'gzip compressed', 'tgz': 'gzip compressed', 'bz2': 'bzip2 compressed', 'zst': 'ZSTD compressed', } def __init__(self, config, output): super().__init__(config, output) self.compress_ext = config.configuration['CompressExtension'] self.valid_src_perms = [int(value, 8) for value in config.configuration['ValidSrcPerms']] self.spec_file = None source_details_dict = { 'source-not-compressed': """A source archive or file in your package is not compressed using the %s compression method (doesn't have the %s extension).""" % (self.compress_ext, self.compress_ext), } self.output.error_details.update(source_details_dict) def check_source(self, pkg): # process file list for fname, pkgfile in pkg.files.items(): self._check_file_ext(fname, pkgfile, pkg) self._check_permissions(fname, pkgfile, pkg) self._check_compressed_source(fname, pkg) self._check_multiple_specfiles(fname, pkg) def _check_file_ext(self, fname, pkgfile, pkg): """ Check if the filename extension is the same as what file(1) says. """ file_ext = fname.rpartition('.')[2] if (file_ext in self.compressed_fileext_magic and pkgfile.magic and self.compressed_fileext_magic[file_ext] not in pkgfile.magic): self.output.add_info('W', pkg, 'inconsistent-file-extension', fname) def _check_permissions(self, fname, pkgfile, pkg): """ Check if the file permissions are valid according to 'ValidSrcPerms' configuration option. """ perm = pkgfile.mode & 0o7777 if perm not in self.valid_src_perms: self.output.add_info('W', pkg, 'strange-permission', fname, '%o' % perm) def _check_compressed_source(self, fname, pkg): """ Check if the Source is compressed if CompressExtension configuration options is used (gz, tgz, bz2, xz or zst). """ if (self.source_regex.search(fname) and self.compress_ext and not fname.endswith(self.compress_ext)): self.output.add_info('W', pkg, 'source-not-compressed', self.compress_ext, fname) def _check_multiple_specfiles(self, fname, pkg): """ Check if the source package contains multiple spec files. """ if fname.endswith('.spec'): if self.spec_file: self.output.add_info('E', pkg, 'multiple-specfiles', self.spec_file, fname) else: self.spec_file = fname rpmlint-2.2.0+ds1/rpmlint/checks/SpecCheck.py000066400000000000000000000730571415540642600210350ustar00rootroot00000000000000from pathlib import Path import re import subprocess from urllib.parse import urlparse import rpm from rpmlint import pkg as Pkg from rpmlint.checks.AbstractCheck import AbstractCheck from rpmlint.helpers import ENGLISH_ENVIROMENT, readlines # Don't check for hardcoded library paths in biarch packages DEFAULT_BIARCH_PACKAGES = '^(gcc|glibc)' def re_tag_compile(tag): rpm_tag = r'^{}\s*:\s*(\S.*?)\s*$'.format(tag) return re.compile(rpm_tag, re.IGNORECASE) patch_regex = re_tag_compile(r'Patch(\d*)') applied_patch_regex = re.compile(r'^%patch(\d*)') applied_patch_p_regex = re.compile(r'\s-P\s+(\d+)\b') applied_patch_pipe_regex = re.compile(r'\s%\{PATCH(\d+)\}\s*\|\s*(%\{?__)?patch\b') applied_patch_i_regex = re.compile(r'(?:%\{?__)?patch\}?.*?\s+(?:<|-i)\s+%\{PATCH(\d+)\}') source_dir_regex = re.compile(r'^[^#]*(\$RPM_SOURCE_DIR|%{?_sourcedir}?)') obsolete_tags_regex = re_tag_compile(r'(?:Serial|Copyright)') buildroot_regex = re_tag_compile('BuildRoot') prefix_regex = re_tag_compile('Prefix') packager_regex = re_tag_compile('Packager') buildarch_regex = re_tag_compile('BuildArch(?:itectures)?') buildprereq_regex = re_tag_compile('BuildPreReq') prereq_regex = re_tag_compile(r'PreReq(\(.*\))') make_check_regex = re.compile(r'(^|\s|%{?__)make}?\s+(check|test)') rm_regex = re.compile(r'(^|\s)((.*/)?rm|%{?__rm}?) ') rpm_buildroot_regex = re.compile(r'^[^#]*(?:(\\\*)\${?RPM_BUILD_ROOT}?|(%+){?buildroot}?)') configure_libdir_spec_regex = re.compile(r'ln |\./configure[^#]*--libdir=(\S+)[^#]*') lib_package_regex = re.compile(r'^%package.*\Wlib') ifarch_regex = re.compile(r'^\s*%ifn?arch\s') if_regex = re.compile(r'^\s*%if\s') endif_regex = re.compile(r'^\s*%endif\b') biarch_package_regex = re.compile(DEFAULT_BIARCH_PACKAGES) libdir_regex = re.compile(r'%{?_lib(?:dir)?\}?\b') section_regexs = dict( ([x, re.compile('^%' + x + r'(?:\s|$)')] for x in ('build', 'changelog', 'check', 'clean', 'description', 'files', 'install', 'package', 'prep') + Pkg.RPM_SCRIPTLETS)) deprecated_grep_regex = re.compile(r'\b[ef]grep\b') # Only check for /lib, /usr/lib, /usr/X11R6/lib # TODO: better handling of X libraries and modules. hardcoded_library_paths = '(/lib|/usr/lib|/usr/X11R6/lib/(?!([^/]+/)+)[^/]*\\.([oa]|la|so[0-9.]*))' hardcoded_library_path_regex = re.compile(r'^[^#]*((^|\s+|\.\./\.\.|\${?RPM_BUILD_ROOT}?|%{?buildroot}?|%{?_prefix}?)' + hardcoded_library_paths + r'(?=[\s;/])([^\s,;]*))') DEFINE_RE = r'(^|\s)%(define|global)\s+' depscript_override_regex = re.compile(DEFINE_RE + r'__find_(requires|provides)\s') depgen_disable_regex = re.compile(DEFINE_RE + r'_use_internal_dependency_generator\s+0') patch_fuzz_override_regex = re.compile(DEFINE_RE + r'_default_patch_fuzz\s+(\d+)') # See https://bugzilla.redhat.com/488146 for details indent_spaces_regex = re.compile('( \t|(^|\t)([^\t]{8})*[^\t]{4}[^\t]?([^\t][^\t.!?]|[^\t]?[.!?] ) )') requires_regex = re.compile(r'^(?:Build)?(?:Pre)?Req(?:uires)?(?:\([^\)]+\))?:\s*(.*)', re.IGNORECASE) provides_regex = re.compile(r'^Provides(?:\([^\)]+\))?:\s*(.*)', re.IGNORECASE) obsoletes_regex = re.compile(r'^Obsoletes:\s*(.*)', re.IGNORECASE) conflicts_regex = re.compile(r'^(?:Build)?Conflicts:\s*(.*)', re.IGNORECASE) compop_regex = re.compile(r'[<>=]') setup_regex = re.compile(r'%setup\b') # intentionally no whitespace before! setup_q_regex = re.compile(r' -[A-Za-z]*q') setup_t_regex = re.compile(r' -[A-Za-z]*T') setup_ab_regex = re.compile(r' -[A-Za-z]*[ab]') autosetup_regex = re.compile(r'^\s*%autosetup(\s.*|$)') autosetup_n_regex = re.compile(r' -[A-Za-z]*N') autopatch_regex = re.compile(r'^\s*%autopatch(?:\s|$)') filelist_regex = re.compile(r'\s+-f\s+\S+') pkgname_regex = re.compile(r'\s+(?:-n\s+)?(\S+)') tarball_regex = re.compile(r'\.(?:t(?:ar|[glx]z|bz2?)|zip)\b', re.IGNORECASE) UNICODE_NBSP = u'\xa0' def unversioned(deps): """Yield unversioned dependency names from the given list.""" for dep in deps: if not dep[1]: yield dep[0] def contains_buildroot(line): """Check if the given line contains use of rpm buildroot.""" res = rpm_buildroot_regex.search(line) if res and \ (not res.group(1) or len(res.group(1)) % 2 == 0) and \ (not res.group(2) or len(res.group(2)) % 2 != 0): return True return False class SpecCheck(AbstractCheck): """Contain check methods that catch errors and warnings in a specfile.""" def __init__(self, config, output): super().__init__(config, output) self._spec_file = None self._spec_name = None self.valid_groups = config.configuration['ValidGroups'] self.output.error_details.update({'non-standard-group': """The value of the Group tag in the package is not valid. Valid groups are: '%s'.""" % ', '.join(self.valid_groups)}) self.hardcoded_lib_path_exceptions_regex = re.compile(config.configuration['HardcodedLibPathExceptions']) def check_source(self, pkg): """Find specfile in SRPM and run spec file related checks.""" wrong_spec = False self._spec_file = None self._spec_name = None # Check if a specfile exist in a specified path for fname, pkgfile in pkg.files.items(): if fname.endswith('.spec'): self._spec_file = pkgfile.path self._spec_name = pkgfile.name if fname == pkg.name + '.spec': wrong_spec = False break else: wrong_spec = True # method call self._check_no_spec_file(pkg) self._check_invalid_spec_name(pkg, wrong_spec) if self._spec_file: # check content of spec file with Pkg.FakePkg(self._spec_file) as package: self.check_spec(package) def check_spec(self, pkg): """Find specfile in specified path and run spec file related checks.""" self._spec_file = pkg.name self._spec_file_dir = str(Path(self._spec_file).parent) spec_only = isinstance(pkg, Pkg.FakePkg) spec_lines = readlines(self._spec_file) patches = {} applied_patches = [] applied_patches_ifarch = [] patches_auto_applied = False source_dir = False buildroot = False configure_linenum = None configure_cmdline = '' mklibname = False is_lib_pkg = False if_depth = 0 ifarch_depth = -1 current_section = 'package' buildroot_clean = {'clean': False, 'install': False} depscript_override = False depgen_disabled = False patch_fuzz_override = False indent_spaces = 0 indent_tabs = 0 section = {} # None == main package current_package = None package_noarch = {} # method call self._check_non_utf8_spec_file(pkg) # gather info from spec lines pkg.current_linenum = 0 nbsp = UNICODE_NBSP # Analyse specfile line by line to check for (E)rrors or (W)arnings for line in spec_lines: pkg.current_linenum += 1 char = line.find(nbsp) if char != -1: self.output.add_info('W', pkg, 'non-break-space', 'line %s, char %d' % (pkg.current_linenum, char)) section_marker = False for sec, regex in section_regexs.items(): res = regex.search(line) if res: current_section = sec section_marker = True section[sec] = section.get(sec, 0) + 1 if sec in ('package', 'files'): rest = filelist_regex.sub('', line[res.end() - 1:]) res = pkgname_regex.search(rest) if res: current_package = res.group(1) else: current_package = None break if section_marker: if not is_lib_pkg and lib_package_regex.search(line): is_lib_pkg = True continue if (current_section in Pkg.RPM_SCRIPTLETS + ('prep', 'build') and contains_buildroot(line)): self.output.add_info('E', pkg, 'rpm-buildroot-usage', '%' + current_section, line[:-1].strip()) if make_check_regex.search(line) and current_section not in \ ('check', 'changelog', 'package', 'description'): self.output.add_info('W', pkg, 'make-check-outside-check-section', line[:-1]) if current_section in buildroot_clean and \ not buildroot_clean[current_section] and \ contains_buildroot(line) and rm_regex.search(line): buildroot_clean[current_section] = True if ifarch_regex.search(line): if_depth = if_depth + 1 ifarch_depth = if_depth if if_regex.search(line): if_depth = if_depth + 1 if setup_regex.match(line): if not setup_q_regex.search(line): # Don't warn if there's a -T without -a or -b if setup_t_regex.search(line): if setup_ab_regex.search(line): self.output.add_info('W', pkg, 'setup-not-quiet') else: self.output.add_info('W', pkg, 'setup-not-quiet') if current_section != 'prep': self.output.add_info('W', pkg, 'setup-not-in-prep') elif autopatch_regex.search(line): patches_auto_applied = True if current_section != 'prep': self.output.add_info('W', pkg, '%autopatch-not-in-prep') else: res = autosetup_regex.search(line) if res: if not autosetup_n_regex.search(res.group(1)): patches_auto_applied = True if current_section != 'prep': self.output.add_info('W', pkg, '%autosetup-not-in-prep') if endif_regex.search(line): if ifarch_depth == if_depth: ifarch_depth = -1 if_depth = if_depth - 1 res = applied_patch_regex.search(line) if res: pnum = res.group(1) or 0 for tmp in applied_patch_p_regex.findall(line) or [pnum]: pnum = int(tmp) applied_patches.append(pnum) if ifarch_depth > 0: applied_patches_ifarch.append(pnum) else: res = applied_patch_pipe_regex.search(line) if res: pnum = int(res.group(1)) applied_patches.append(pnum) if ifarch_depth > 0: applied_patches_ifarch.append(pnum) else: res = applied_patch_i_regex.search(line) if res: pnum = int(res.group(1)) applied_patches.append(pnum) if ifarch_depth > 0: applied_patches_ifarch.append(pnum) if not res and not source_dir: res = source_dir_regex.search(line) if res: source_dir = True self.output.add_info('E', pkg, 'use-of-RPM_SOURCE_DIR') if configure_linenum: if configure_cmdline[-1] == '\\': configure_cmdline = configure_cmdline[:-1] + line.strip() else: res = configure_libdir_spec_regex.search(configure_cmdline) if not res: # Hack to get the correct (start of ./configure) line # number displayed: real_linenum = pkg.current_linenum pkg.current_linenum = configure_linenum self.output.add_info('W', pkg, 'configure-without-libdir-spec') pkg.current_linenum = real_linenum elif res.group(1): res = re.match(hardcoded_library_paths, res.group(1)) if res: self.output.add_info('E', pkg, 'hardcoded-library-path', res.group(1), 'in configure options') configure_linenum = None hash_pos = line.find('#') if current_section != 'changelog': cfg_pos = line.find('./configure') if cfg_pos != -1 and (hash_pos == -1 or hash_pos > cfg_pos): # store line where it started configure_linenum = pkg.current_linenum configure_cmdline = line.strip() res = hardcoded_library_path_regex.search(line) if current_section != 'changelog' and res and not \ (biarch_package_regex.match(pkg.name) or self.hardcoded_lib_path_exceptions_regex.search( res.group(1).lstrip())): self.output.add_info('E', pkg, 'hardcoded-library-path', 'in', res.group(1).lstrip()) if '%mklibname' in line: mklibname = True if current_section == 'package': # Would be cleaner to get sources and patches from the # specfile parsed in Python (see below), but we want to # catch %ifarch'd etc ones as well, and also catch these when # the specfile is not parseable. res = patch_regex.search(line) if res: pnum = int(res.group(1) or 0) patches[pnum] = res.group(2) res = obsolete_tags_regex.search(line) if res: self.output.add_info('W', pkg, 'obsolete-tag', res.group(1)) res = buildroot_regex.search(line) if res: buildroot = True if res.group(1).startswith('/'): self.output.add_info('W', pkg, 'hardcoded-path-in-buildroot-tag', res.group(1)) res = buildarch_regex.search(line) if res: if res.group(1) != 'noarch': self.output.add_info('E', pkg, 'buildarch-instead-of-exclusivearch-tag', res.group(1)) else: package_noarch[current_package] = True res = packager_regex.search(line) if res: self.output.add_info('W', pkg, 'hardcoded-packager-tag', res.group(1)) res = prefix_regex.search(line) if res: if not res.group(1).startswith('%'): self.output.add_info('W', pkg, 'hardcoded-prefix-tag', res.group(1)) res = prereq_regex.search(line) if res: self.output.add_info('E', pkg, 'prereq-use', res.group(2)) res = buildprereq_regex.search(line) if res: self.output.add_info('E', pkg, 'buildprereq-use', res.group(1)) res = requires_regex.search(line) if res: reqs = Pkg.parse_deps(res.group(1)) deptoken = Pkg.has_forbidden_controlchars(reqs) if deptoken: self.output.add_info('E', pkg, 'forbidden-controlchar-found', f'Requires: {deptoken}') for req in unversioned(reqs): if compop_regex.search(req): self.output.add_info('W', pkg, 'comparison-operator-in-deptoken', req) res = provides_regex.search(line) if res: provs = Pkg.parse_deps(res.group(1)) deptoken = Pkg.has_forbidden_controlchars(provs) if deptoken: self.output.add_info('E', pkg, 'forbidden-controlchar-found', f'Provides: {deptoken}') for prov in unversioned(provs): if not prov.startswith('/'): self.output.add_info('W', pkg, 'unversioned-explicit-provides', prov) if compop_regex.search(prov): self.output.add_info('W', pkg, 'comparison-operator-in-deptoken', prov) res = obsoletes_regex.search(line) if res: obses = Pkg.parse_deps(res.group(1)) deptoken = Pkg.has_forbidden_controlchars(obses) if deptoken: self.output.add_info('E', pkg, 'forbidden-controlchar-found', f'Obsoletes: {deptoken}') for obs in unversioned(obses): if not obs.startswith('/'): self.output.add_info('W', pkg, 'unversioned-explicit-obsoletes', obs) if compop_regex.search(obs): self.output.add_info('W', pkg, 'comparison-operator-in-deptoken', obs) res = conflicts_regex.search(line) if res: confs = Pkg.parse_deps(res.group(1)) deptoken = Pkg.has_forbidden_controlchars(confs) if deptoken: self.output.add_info('E', pkg, 'forbidden-controlchar-found', f'Conflicts: {deptoken}') for conf in unversioned(confs): if compop_regex.search(conf): self.output.add_info('W', pkg, 'comparison-operator-in-deptoken', conf) if current_section == 'changelog': deptoken = Pkg.has_forbidden_controlchars(line) if deptoken: self.output.add_info('E', pkg, 'forbidden-controlchar-found', '%%changelog: %s' % deptoken) for match in self.macro_regex.findall(line): res = re.match('%+', match) if len(res.group(0)) % 2 and match != '%autochangelog': self.output.add_info('W', pkg, 'macro-in-%changelog', match) else: if not depscript_override: depscript_override = \ depscript_override_regex.search(line) is not None if not depgen_disabled: depgen_disabled = \ depgen_disable_regex.search(line) is not None if not patch_fuzz_override: patch_fuzz_override = \ patch_fuzz_override_regex.search(line) is not None if current_section == 'files': # TODO: check scriptlets for these too? if package_noarch.get(current_package) or \ (current_package not in package_noarch and package_noarch.get(None)): res = libdir_regex.search(line) if res: pkgname = current_package if pkgname is None: pkgname = '(main package)' self.output.add_info('W', pkg, 'libdir-macro-in-noarch-package', pkgname, line.rstrip()) if not indent_tabs and '\t' in line: indent_tabs = pkg.current_linenum if not indent_spaces and indent_spaces_regex.search(line): indent_spaces = pkg.current_linenum # Check if egrep or fgrep is used if current_section not in \ ('package', 'changelog', 'description', 'files'): greps = deprecated_grep_regex.findall(line) if greps: self.output.add_info('W', pkg, 'deprecated-grep', greps) # If not checking spec file only, we're checking one inside a # SRPM -> skip this check to avoid duplicate warnings (#167) if spec_only and self.valid_groups and \ line.lower().startswith('group:'): group = line[6:].strip() if group not in self.valid_groups: self.output.add_info('W', pkg, 'non-standard-group', group) # Test if there are macros in comments if hash_pos != -1 and \ (hash_pos == 0 or line[hash_pos - 1] in (' ', '\t')): for match in self.macro_regex.findall( line[hash_pos + 1:]): res = re.match('%+', match) if len(res.group(0)) % 2: self.output.add_info('W', pkg, 'macro-in-comment', match) # Last line read is not useful after this point pkg.current_linenum = None # Run checks for whole package self._check_no_cleaning_of_buildroot(pkg, buildroot_clean) self._check_no_buildroot_tag(pkg, buildroot) self._check_no_s_section(pkg, section) self._check_superfluous_clean_section(pkg, section) self._check_more_than_one_changelog_section(pkg, section) self._check_lib_package_without_mklibname(pkg, is_lib_pkg, mklibname) self._check_descript_without_disabling_depgen(pkg, depscript_override, depgen_disabled) self._check_patch_fuzz_is_changed(pkg, patch_fuzz_override) self._check_mixed_use_of_space_and_tabs(pkg, indent_spaces, indent_tabs) self.check_ifarch_and_not_applied_patches(pkg, patches_auto_applied, patches, applied_patches_ifarch, applied_patches) # Checks below require a real spec file if not self._spec_file: return self._check_specfile_error(pkg) self._check_invalid_url(pkg, rpm) def _check_no_spec_file(self, pkg): """Check if no spec file is found in RPM meta data.""" if not self._spec_file: self.output.add_info('E', pkg, 'no-spec-file') def _check_invalid_spec_name(self, pkg, wrong_spec): """Check if spec file has same name as the 'Name: ' tag.""" if wrong_spec and self._spec_file: self.output.add_info('E', pkg, 'invalid-spec-name') def _check_non_utf8_spec_file(self, pkg): """Check if spec file has UTF-8 character encoding.""" if self._spec_file: if not Pkg.is_utf8(self._spec_file): self.output.add_info('E', pkg, 'non-utf8-spec-file', self._spec_name or self._spec_file) def _check_no_cleaning_of_buildroot(self, pkg, buildroot_clean): """Check if specfile has $RPM_BUILD_ROOT in the %clean section in the beginning of the %install section. """ for sect in (x for x in buildroot_clean if not buildroot_clean[x]): self.output.add_info('W', pkg, 'no-cleaning-of-buildroot', '%' + sect) def _check_no_buildroot_tag(self, pkg, buildroot): """Check if BuildRoot tag is used in the specfile.""" if not buildroot: self.output.add_info('W', pkg, 'no-buildroot-tag') def _check_no_s_section(self, pkg, section): """Check if there is no (%prep, %build, %install) in the specfile. """ for sec in ('prep', 'build', 'install'): if not section.get(sec): self.output.add_info('W', pkg, 'no-%%%s-section' % sec) def _check_superfluous_clean_section(self, pkg, section): """Check for a superfluous %clean section in the specfile. """ if section.get('clean'): self.output.add_info('E', pkg, 'superfluous-%clean-section') def _check_more_than_one_changelog_section(self, pkg, section): """Check if specfile has more than one %changelog. prep, build, install, check prevented by rpmbuild 4.4 """ if section.get('changelog', 0) > 1: self.output.add_info('W', pkg, 'more-than-one-%changelog-section') def _check_lib_package_without_mklibname(self, pkg, is_lib_pkg, mklibname): """Check if package name is built using %mklibname to allow lib64 and lib32 coexistence. This check is specific to Mandriva and it's derivatives, check issue #9 in rpm-software-management/rpmlint/issues """ if is_lib_pkg and not mklibname: self.output.add_info('E', pkg, 'lib-package-without-%mklibname') def _check_descript_without_disabling_depgen(self, pkg, depscript_override, depgen_disabled): """Check if specfile has %define _use_internal_dependency_generator set to 0 to disable it, or does not have define __find_provides/requires. """ if depscript_override and not depgen_disabled: self.output.add_info('W', pkg, 'depscript-without-disabling-depgen') def _check_patch_fuzz_is_changed(self, pkg, patch_fuzz_override): """Check if specfile has internal patch fuzz was changed.""" if patch_fuzz_override: self.output.add_info('W', pkg, 'patch-fuzz-is-changed') def _check_mixed_use_of_space_and_tabs(self, pkg, indent_spaces, indent_tabs): """Check if specfile has mixed uses of spaces and tabs.""" if indent_spaces and indent_tabs: pkg.current_linenum = max(indent_spaces, indent_tabs) self.output.add_info('W', pkg, 'mixed-use-of-spaces-and-tabs', '(spaces: line %d, tab: line %d)' % (indent_spaces, indent_tabs)) pkg.current_linenum = None def check_ifarch_and_not_applied_patches(self, pkg, patches_auto_applied, patches, applied_patches_ifarch, applied_patches): """Check if specfile has a patch applied inside an %ifarch block. and check if a patch was included but not applied.""" if not patches_auto_applied: for pnum, pfile in patches.items(): if pnum in applied_patches_ifarch: self.output.add_info('W', pkg, '%ifarch-applied-patch', 'Patch%d:' % pnum, pfile) # Check if a patch is included in specfile but was not applied. if pnum not in applied_patches: self.output.add_info('W', pkg, 'patch-not-applied', 'Patch%d:' % pnum, pfile) def _check_specfile_error(self, pkg): """It parse the specfile with rpm and forward errors to rpmlint output.""" # We'd like to parse the specfile only once using python bindings, # but it seems errors from rpmlib get logged to stderr and we can't # capture and print them nicely, so we do it once each way :P try: outcmd = subprocess.run( ('rpm', '-q', '--qf=', '-D', '_sourcedir %s' % self._spec_file_dir, '--specfile', self._spec_file), stderr=subprocess.PIPE, encoding='utf8', env=ENGLISH_ENVIROMENT) for line in outcmd.stderr.splitlines(): line = line.strip() if line and 'warning:' not in line: self.output.add_info('E', pkg, 'specfile-error', line) except UnicodeDecodeError as e: self.output.add_info('E', pkg, 'specfile-error', str(e)) def _check_invalid_url(self, pkg, rpm): """Check if specfile has an invalid url.""" # grab sources and patches from parsed spec object to get # them with macros expanded for URL checking spec_obj = None rpm.addMacro('_sourcedir', self._spec_file_dir) try: transaction_set = rpm.TransactionSet() spec_obj = transaction_set.parseSpec(str(self._spec_file)) except (ValueError, rpm.error) as e: self.output.add_info('E', pkg, 'specfile-error', str(e).strip(), str(self._spec_file)) rpm.delMacro('_sourcedir') if spec_obj: for src in spec_obj.sources: (url, num, flags) = src (scheme, netloc) = urlparse(url)[0:2] if flags & 1: # rpmspec.h, rpm.org ticket #123 srctype = 'Source' else: srctype = 'Patch' tag = '%s%s' % (srctype, num) if scheme and netloc: continue elif srctype == 'Source' and tarball_regex.search(url): self.output.add_info('W', pkg, 'invalid-url', '%s:' % tag, url) rpmlint-2.2.0+ds1/rpmlint/checks/SysVInitOnSystemdCheck.py000066400000000000000000000037151415540642600235330ustar00rootroot00000000000000from pathlib import Path from rpmlint.checks.AbstractCheck import AbstractCheck class SysVInitOnSystemdCheck(AbstractCheck): def __init__(self, config, output): super().__init__(config, output) self.initscripts = set() self.bootscripts = set() self.systemdscripts = set() def check(self, pkg): if pkg.is_source: return self._find_services_and_scripts(pkg) for req in pkg.requires + pkg.prereq: if req[0] == 'insserv': self.output.add_info('E', pkg, 'obsolete-insserv-requirement') for filename in self.bootscripts: self.output.add_info('E', pkg, 'deprecated-boot-script', filename) for filename in self.initscripts: self.output.add_info('E', pkg, 'deprecated-init-script', filename) for filename in self.initscripts: if filename in self.systemdscripts: self.output.add_info('E', pkg, 'systemd-shadowed-initscript', filename) def _find_services_and_scripts(self, pkg): # Find all regular systemd services and initscripts for filename, _pkgfile in pkg.files.items(): if filename in pkg.ghost_files: continue if filename.startswith('/usr/lib/systemd/system/'): basename = Path(filename).name # @ means it is socket service which is not what we look for if '@' in filename: continue if filename.endswith('.service') or filename.endswith('.target'): self.systemdscripts.add(basename.rpartition('.')[0]) if filename.startswith('/etc/init.d/') or filename.startswith('/etc/rc.d/init.d'): basename = Path(filename).name if basename.startswith('boot.'): self.bootscripts.add(basename) elif not basename.startswith('rc'): self.initscripts.add(basename) rpmlint-2.2.0+ds1/rpmlint/checks/TagsCheck.py000066400000000000000000001070031415540642600210260ustar00rootroot00000000000000import calendar from pathlib import Path import re import time from urllib.parse import urlparse import rpm from rpmlint import pkg as Pkg from rpmlint.checks import FilesCheck from rpmlint.checks.AbstractCheck import AbstractCheck from rpmlint.helpers import byte_to_string from rpmlint.spellcheck import Spellcheck CAPITALIZED_IGNORE_LIST = ('jQuery', 'openSUSE', 'wxWidgets', 'a', 'an', 'uWSGI') changelog_version_regex = re.compile(r'[^>]([^ >]+)\s*$') changelog_text_version_regex = re.compile(r'^\s*-\s*((\d+:)?[\w\.]+-[\w\.]+)') devel_number_regex = re.compile(r'(.*?)([0-9.]+)(_[0-9.]+)?-devel') lib_devel_number_regex = re.compile(r'^lib(.*?)([0-9.]+)(_[0-9.]+)?-devel') lib_package_regex = re.compile(r'(?:^(?:compat-)?lib.*?(\.so.*)?|libs?[\d-]*)$', re.IGNORECASE) leading_space_regex = re.compile(r'^\s+') pkg_config_regex = re.compile(r'^/usr/(?:lib\d*|share)/pkgconfig/') license_regex = re.compile(r'\(([^)]+)\)|\s(?:and|or|AND|OR)\s') license_exception_regex = re.compile(r'(\S+)\s(?:WITH|with)\s(\S+)') invalid_version_regex = re.compile(r'([0-9](?:rc|alpha|beta|pre).*)', re.IGNORECASE) # () are here for grouping purpose in the regexp tag_regex = re.compile(r'^((?:Auto(?:Req|Prov|ReqProv)|Build(?:Arch(?:itectures)?|Root)|(?:Build)?Conflicts|(?:Build)?(?:Pre)?Requires|Copyright|(?:CVS|SVN)Id|Dist(?:ribution|Tag|URL)|DocDir|(?:Build)?Enhances|Epoch|Exclu(?:de|sive)(?:Arch|OS)|Group|Icon|License|Name|No(?:Patch|Source)|Obsoletes|Packager|Patch\d*|Prefix(?:es)?|Provides|(?:Build)?Recommends|Release|RHNPlatform|Serial|Source\d*|(?:Build)?Suggests|Summary|(?:Build)?Supplements|(?:Bug)?URL|Vendor|Version)(?:\([^)]+\))?:)\s*\S', re.IGNORECASE) punct = '.,:;!?' so_dep_regex = re.compile(r'\.so(\.[0-9a-zA-Z]+)*(\([^)]*\))*$') # we assume that no rpm packages existed before rpm itself existed... oldest_changelog_timestamp = calendar.timegm(time.strptime('1995-01-01', '%Y-%m-%d')) class TagsCheck(AbstractCheck): def __init__(self, config, output): super().__init__(config, output) self.valid_groups = config.configuration['ValidGroups'] self.valid_licenses = config.configuration['ValidLicenses'] self.invalid_requires = map(re.compile, config.configuration['InvalidRequires']) self.packager_regex = re.compile(config.configuration['Packager']) self.release_ext = config.configuration['ReleaseExtension'] self.extension_regex = self.release_ext and re.compile(self.release_ext) self.use_version_in_changelog = config.configuration['UseVersionInChangelog'] self.invalid_url_regex = re.compile(config.configuration['InvalidURL'], re.IGNORECASE) self.forbidden_words_regex = re.compile(r'(%s)' % config.configuration['ForbiddenWords'], re.IGNORECASE) self.valid_buildhost_regex = re.compile(config.configuration['ValidBuildHost']) self.use_epoch = config.configuration['UseEpoch'] self.max_line_len = config.configuration['MaxLineLength'] self.spellcheck = config.configuration['UseEnchant'] self.valid_license_exceptions = config.configuration['ValidLicenseExceptions'] if self.spellcheck: self.spellchecker = Spellcheck() for i in ('obsoletes', 'conflicts', 'provides', 'recommends', 'suggests', 'enhances', 'supplements'): self.output.error_details.update({'no-epoch-in-{}'.format(i): 'Your package contains a versioned %s entry without an Epoch.' % i.capitalize()}) self.output.error_details.update({'non-standard-group': """The value of the Group tag in the package is not valid. Valid groups are: '%s'.""" % ', '.join(self.valid_groups), 'not-standard-release-extension': 'Your release tag must match the regular expression ' + self.release_ext + '.', 'summary-too-long': "The 'Summary:' must not exceed %d characters." % self.max_line_len, 'description-line-too-long': """Your description lines must not exceed %d characters. If a line is exceeding this number, cut it to fit in two lines.""" % self.max_line_len, 'invalid-license': """The value of the License tag was not recognized. Known values are: '%s'.""" % ', '.join(self.valid_licenses), }) def _unexpanded_macros(self, pkg, tagname, value, is_url=False): if not value: return if not isinstance(value, (list, tuple)): value = [value] for val in value: for match in self.macro_regex.findall(val): # Do not warn about %XX URL escapes if is_url and re.match('^%[0-9A-F][0-9A-F]$', match, re.I): continue self.output.add_info('W', pkg, 'unexpanded-macro', tagname, match) def check(self, pkg): """Contains methods that checks tags and values in a spec file of a package.""" version = pkg[rpm.RPMTAG_VERSION] release = pkg[rpm.RPMTAG_RELEASE] epoch = pkg[rpm.RPMTAG_EPOCH] group = pkg[rpm.RPMTAG_GROUP] buildhost = pkg[rpm.RPMTAG_BUILDHOST] langs = pkg[rpm.RPMTAG_HEADERI18NTABLE] summary = byte_to_string(pkg[rpm.RPMTAG_SUMMARY]) description = byte_to_string(pkg[rpm.RPMTAG_DESCRIPTION]) changelog = pkg[rpm.RPMTAG_CHANGELOGNAME] rpm_license = pkg[rpm.RPMTAG_LICENSE] name = pkg.name deps = pkg.requires + pkg.prereq is_devel = FilesCheck.devel_regex.search(name) is_source = pkg.is_source # List of words to ignore in spell check ignored_words = set() for pf in pkg.files: ignored_words.update(pf.split('/')) for tag in ('provides', 'requires', 'conflicts', 'obsoletes'): ignored_words.update((x[0] for x in 'pkg.' + str(tag))) # Run checks for whole package self._check_invalid_packager(pkg) self._check_invalid_version_and_no_version_tag(pkg, version) self._check_non_standard_release_extension(pkg, release) self._check_no_epoch_tag(pkg, epoch) self._check_no_epoch_in_tags(pkg) self._check_multiple_dependencies(pkg, deps, is_devel, is_source) self._unexpanded_macros(pkg, 'Name', name) self._check_multiple_tags(pkg, name, is_devel, is_source, deps, epoch, version) self._check_summary_tag(pkg, summary, langs, ignored_words) self._check_description_tag(pkg, description, langs, ignored_words) self._check_group_tag(pkg, group) self._check_buildhost_tag(pkg, buildhost) self._check_changelog_tag(pkg, changelog, version, release, name, epoch) self._check_license(pkg, rpm_license) self._check_url(pkg) prov_names = [x[0] for x in pkg.provides] self._check_obsolete_not_provided(pkg, prov_names) for dep_token in pkg.obsoletes: value = Pkg.formatRequire(*dep_token) self._unexpanded_macros(pkg, 'Obsoletes {}'.format(value,), value) self._check_useless_provides(pkg, prov_names) self._check_forbidden_controlchar(pkg) self._check_self_obsoletion(pkg) self._check_non_coherent_filename(pkg) for tag in ('Distribution', 'DistTag', 'ExcludeArch', 'ExcludeOS', 'Vendor'): if hasattr(rpm, 'RPMTAG_%s' % tag.upper()): res = byte_to_string(pkg[getattr(rpm, 'RPMTAG_%s' % tag.upper())]) self._unexpanded_macros(pkg, tag, res) def check_description(self, pkg, lang, ignored_words): description = pkg.langtag(rpm.RPMTAG_DESCRIPTION, lang) description = byte_to_string(description) self._unexpanded_macros(pkg, '%%description -l %s' % lang, description) if self.spellcheck: pkgname = byte_to_string(pkg.header[rpm.RPMTAG_NAME]) typos = self.spellchecker.spell_check(description, '%description -l {}', lang, pkgname, ignored_words) for typo in typos.items(): self.output.add_info('E', pkg, 'spelling-error', typo) for i in description.splitlines(): if len(i) > self.max_line_len: self.output.add_info('E', pkg, 'description-line-too-long', self._lang_for_error(lang), i) res = self.forbidden_words_regex.search(i) if res and self.config.configuration['ForbiddenWords']: self.output.add_info('W', pkg, 'description-use-invalid-word', self._lang_for_error(lang), res.group(1)) res = tag_regex.search(i) if res: self.output.add_info('W', pkg, 'tag-in-description', self._lang_for_error(lang), res.group(1)) def _lang_for_error(self, lang): return lang if lang != 'C' and lang != 'C.UTF-8' else None def check_summary(self, pkg, lang, ignored_words): summary = pkg.langtag(rpm.RPMTAG_SUMMARY, lang) summary = byte_to_string(summary) self._unexpanded_macros(pkg, 'Summary(%s)' % lang, summary) if self.spellcheck: pkgname = byte_to_string(pkg.header[rpm.RPMTAG_NAME]) typos = self.spellchecker.spell_check(summary, 'Summary({})', lang, pkgname, ignored_words) for typo in typos.items(): self.output.add_info('E', pkg, 'spelling-error', typo) if any(nl in summary for nl in ('\n', '\r')): self.output.add_info('E', pkg, 'summary-on-multiple-lines', self._lang_for_error(lang)) if (summary[0] != summary[0].upper() and summary.partition(' ')[0] not in CAPITALIZED_IGNORE_LIST): self.output.add_info('W', pkg, 'summary-not-capitalized', self._lang_for_error(lang), summary) if summary[-1] == '.': self.output.add_info('W', pkg, 'summary-ended-with-dot', self._lang_for_error(lang), summary) if len(summary) > self.max_line_len: self.output.add_info('E', pkg, 'summary-too-long', self._lang_for_error(lang), summary) if leading_space_regex.search(summary): self.output.add_info('E', pkg, 'summary-has-leading-spaces', self._lang_for_error(lang), summary) res = self.forbidden_words_regex.search(summary) if res and self.config.configuration['ForbiddenWords']: self.output.add_info('W', pkg, 'summary-use-invalid-word', self._lang_for_error(lang), res.group(1)) if pkg.name: sepchars = r'[\s%s]' % punct res = re.search(r'(?:^|\s)(%s)(?:%s|$)' % (re.escape(pkg.name), sepchars), summary, re.IGNORECASE | re.UNICODE) if res: self.output.add_info('W', pkg, 'name-repeated-in-summary', self._lang_for_error(lang), res.group(1)) def _check_invalid_packager(self, pkg): """Trigger invalid-packager and no-packager-tag The packager email must end with an email compatible with the Packager option of rpmlint. Please change it and rebuild your package. Args: pkg: Variable used to store package name in STDOUT Returns: Output info to STDOUT """ packager = pkg[rpm.RPMTAG_PACKAGER] if packager: self._unexpanded_macros(pkg, 'Packager', packager) if self.config.configuration['Packager'] and \ not self.packager_regex.search(packager): self.output.add_info('W', pkg, 'invalid-packager', packager) else: self.output.add_info('E', pkg, 'no-packager-tag') def _check_invalid_version_and_no_version_tag(self, pkg, version): """Trigger check invalid-version, no-version-tag. Args: version: Variable used to find Version: value tag in rpm package Returns: Output info to STDOUT """ if version: self._unexpanded_macros(pkg, 'Version', version) res = invalid_version_regex.search(version) # Check if a package has a version tag value start with # pre, alpha, beta or rc suffixes if res: self.output.add_info('E', pkg, 'invalid-version', version) # Check if a package has no Version: tag in its spec file else: self.output.add_info('E', pkg, 'no-version-tag') def _check_non_standard_release_extension(self, pkg, release): """Trigger check not-standard-release-extension, no-release-tag Args: release: Variable checks Realease: tag value Returns: Output info to STDOUT """ if release: self._unexpanded_macros(pkg, 'Release', release) # [This check is dynamically produced] # Check if the release tag matches the regex expression self.release_ext if self.release_ext and not self.extension_regex.search(release): self.output.add_info('W', pkg, 'not-standard-release-extension', release) # Check if there is no Release tag in spec file else: self.output.add_info('E', pkg, 'no-release-tag') def _check_no_epoch_tag(self, pkg, epoch): """Trigger check no-epoch-tag, unreasonable-epoch Args: epoch: Finds the Epoch: tag Returns: Output info to STDOUT """ if epoch is None: # Check if a package does not contain an Epoch: tag if self.use_epoch: self.output.add_info('E', pkg, 'no-epoch-tag') else: # Check if a package has an Epoch: value of greater than 99 if epoch > 99: self.output.add_info('W', pkg, 'unreasonable-epoch', epoch) def _check_no_epoch_in_tags(self, pkg): """Trigger check no-epoch-in-{} multiple tags Check if versioned dependency is not used in tags even when UseEpoch is set to true and trigger checks in tags ['Obsoletes', 'Conflicts', 'Provides', 'Recommends', 'Suggests', 'Enhances', 'Supplements'] Returns: Output info to STDOUT """ if self.use_epoch: for tag in ('obsoletes', 'conflicts', 'provides', 'recommends', 'suggests', 'enhances', 'supplements'): for x in (x for x in getattr(pkg, tag)() if x[1] and x[2][0] is None): self.output.add_info('W', pkg, 'no-epoch-in-{}'.format(tag), Pkg.formatRequire(*x)) def _check_multiple_dependencies(self, pkg, deps, is_source, is_devel): """Contain multiple check, no-epoch-in-dependency, invalid-dependency, invalid-build-requires, devel-dependency, explicit-devel-dependency Args: deps: Variable to find PreReq and Requires tag is_source: Variable to check if a package is of source type is_devel: The param to check if a package name ends with *-devel Returns: Output info to STDOUT example: tmp.x86_64: W: requires-on-release foo = 2.1-1 """ devel_depend = False for dep in deps: value = Pkg.formatRequire(*dep) # Check if a package has a versioned dependency in spec file without Epoch: tag if self.use_epoch and dep[1] and dep[2][0] is None and \ not dep[0].startswith('rpmlib('): self.output.add_info('W', pkg, 'no-epoch-in-dependency', value) # Check if a package has a invalid-dependency in spec file for req in self.invalid_requires: if req.search(dep[0]): self.output.add_info('E', pkg, 'invalid-dependency', dep[0]) # Check if a dependency requirement starts with /usr/local # For Ex:- Requires: /usr/local/something if dep[0].startswith('/usr/local/'): self.output.add_info('E', pkg, 'invalid-dependency', dep[0]) # Check if a package contains a dependency whose name is not docile with # lib64 naming standards. if is_source: if lib_devel_number_regex.search(dep[0]): self.output.add_info('E', pkg, 'invalid-build-requires', dep[0]) # Check if a package containing a devel dependency # is not a devel package itself elif not is_devel: if not devel_depend and FilesCheck.devel_regex.search(dep[0]): self.output.add_info('E', pkg, 'devel-dependency', dep[0]) devel_depend = True if not dep[1]: res = lib_package_regex.search(dep[0]) # Check if a package cannot find the lib dependencies by itself # without the packager using explicit Requires: TagsCheck # For Ex:- Requires: lib* if res and not res.group(1): self.output.add_info('E', pkg, 'explicit-lib-dependency', dep[0]) # Check if a package requires a specfic version of another package. # For Ex:- Requires: python==3.8 if dep[1] == rpm.RPMSENSE_EQUAL and dep[2][2] is not None: self.output.add_info('W', pkg, 'requires-on-release', value) self._unexpanded_macros(pkg, 'dependency {}'.format(value,), value) def _check_multiple_tags(self, pkg, name, is_devel, is_source, deps, epoch, version): """Trigger checks no-name-tag check, no-version-dependency-on, missing-dependency-on, no-major-in-name, no-provides, no-pkg-config-provides Args: name: Variable to find if Name: tag Returns: Output info to STDOUT """ if not name: # Check if a package does not have a Name: tag self.output.add_info('E', pkg, 'no-name-tag') else: if is_devel and not is_source: base = is_devel.group(1) dep = None has_so = False has_pc = False for fname in pkg.files: if fname.endswith('.so'): has_so = True if pkg_config_regex.match(fname) and fname.endswith('.pc'): has_pc = True if has_so: base_or_libs = base + '*' + '/' + base + '-libs/lib' + base + '*' # try to match *%_isa as well (e.g. '(x86-64)', '(x86-32)') base_or_libs_re = re.compile( r'^(lib)?%s(-libs)?[\d_-]*(\(\w+-\d+\))?$' % re.escape(base)) for d in deps: if base_or_libs_re.match(d[0]): dep = d break if dep and version: exp = (epoch, version, None) sexp = Pkg.versionToString(exp) if not dep[1]: self.output.add_info('W', pkg, 'no-version-dependency-on', base_or_libs, sexp) elif dep[2][:2] != exp[:2]: version = Pkg.versionToString((dep[2][0], dep[2][1], None)) self.output.add_info('W', pkg, 'missing-dependency-on', f'{base_or_libs} = {version}') res = devel_number_regex.search(name) if not res: self.output.add_info('W', pkg, 'no-major-in-name', name) else: if res.group(3): prov = res.group(1) + res.group(2) + '-devel' else: prov = res.group(1) + '-devel' if prov not in (x[0] for x in pkg.provides): self.output.add_info('W', pkg, 'no-provides', prov) if has_pc: found_pkg_config_dep = False for p in (x[0] for x in pkg.provides): if p.startswith('pkgconfig('): found_pkg_config_dep = True break if not found_pkg_config_dep: self.output.add_info('E', pkg, 'no-pkg-config-provides') def _check_summary_tag(self, pkg, summary, langs, ignored_words): """Trigger check no-summary-tag Check if a package does not have a summary tag Args: summary: Variable to find Summary: tag langs: Variable to find RPMTAG_HEADERI18NTABLE which Contains a list of locales for which strings are provided in other parts of the package. ignored_words: Find ignored words list in the Require: tag Returns: Output info to STDOUT """ if summary: if not langs: self._unexpanded_macros(pkg, 'Summary', summary) else: for lang in langs: self.check_summary(pkg, lang, ignored_words) else: self.output.add_info('E', pkg, 'no-summary-tag') def _check_description_tag(self, pkg, description, langs, ignored_words): """Trigger check description-shorter-than-summary, no-description-tag Args: description: Find %description tag in package Returns: Output info to STDOUT """ if description: if not langs: self._unexpanded_macros(pkg, '%description', description) else: for lang in langs: self.check_description(pkg, lang, ignored_words) # Check if a package has a description shorter than Summary if len(description) < len(pkg[rpm.RPMTAG_SUMMARY]): self.output.add_info('W', pkg, 'description-shorter-than-summary') else: # Check if a package does not have a %description tag in spec file self.output.add_info('E', pkg, 'no-description-tag') def _check_group_tag(self, pkg, group): """Trigger check no-group-tag, devel-package-with-non-devel-group, non-standard-group Args: group: Find Group: tag in package Returns: Output info to STDOUT """ self._unexpanded_macros(pkg, 'Group', group) # Check if a package does not have a group tag if not group: self.output.add_info('E', pkg, 'no-group-tag') # Check if a package name end with -devel but # has a Group: tag with value start other than Development/ elif pkg.name.endswith('-devel') and not group.startswith('Development/'): self.output.add_info('W', pkg, 'devel-package-with-non-devel-group', group) # Check if a package has a non-standard-group # which does not comply with the standard group list elif self.valid_groups and group not in self.valid_groups: self.output.add_info('W', pkg, 'non-standard-group', group) def _check_buildhost_tag(self, pkg, buildhost): """Trigger check no-buildhost-tag, invalid-buildhost Args: buildhost: Variable to find BuildHost: tag_regex Returns: Output info to STDOUT """ self._unexpanded_macros(pkg, 'BuildHost', buildhost) # Check if a package has no buildhost tag if not buildhost: self.output.add_info('E', pkg, 'no-buildhost-tag') # Check if a package has a invalid-buildhost which does not comply # with configuration ValidBuildHost elif self.config.configuration['ValidBuildHost'] and \ not self.valid_buildhost_regex.search(buildhost): self.output.add_info('W', pkg, 'invalid-buildhost', buildhost) def _check_changelog_tag(self, pkg, changelog, version, release, name, epoch): """Trigger multiple check of type *-changelog, *-changelogname-*, changelog-* and forbidden-controlchar Contains all the checks that cause an issue during build of the rpm in the %changelog of the specfile Args: changelog: Find the %changelog in the specfile Returns: Output info to STDOUT """ # Check if a package does not have a %changelog in its spec file if not changelog: self.output.add_info('E', pkg, 'no-changelogname-tag') else: clt = pkg[rpm.RPMTAG_CHANGELOGTEXT] if self.use_version_in_changelog: ret = changelog_version_regex.search(byte_to_string(changelog[0])) if not ret and clt: # we also allow the version specified as the first # thing on the first line of the text ret = changelog_text_version_regex.search(byte_to_string(clt[0])) # Check if a package does not have version in the %changelog in latest version if not ret: self.output.add_info('W', pkg, 'no-version-in-last-changelog') elif version and release: srpm = pkg[rpm.RPMTAG_SOURCERPM] or '' # only check when source name correspond to name if srpm[0:-8] == '%s-%s-%s' % (name, version, release): expected = [version + '-' + release] if epoch is not None: # regardless of use_epoch expected[0] = str(epoch) + ':' + expected[0] # Allow EVR in changelog without release extension, # the extension is often a macro or otherwise dynamic. if self.release_ext: expected.append(self.extension_regex.sub('', expected[0])) # Check if a package does not have a version that is # compatible with epoch:vesrion-release tuple if ret.group(1) not in expected: if len(expected) == 1: expected = expected[0] self.output.add_info('W', pkg, 'incoherent-version-in-changelog', ret.group(1), expected) if clt: changelog = changelog + clt for deptoken in changelog: dep = Pkg.has_forbidden_controlchars(deptoken) # Check if a package contains a forbidden character in %changelog if dep: self.output.add_info('E', pkg, 'forbidden-controlchar-found', '%%changelog : %s' % dep) break clt = pkg[rpm.RPMTAG_CHANGELOGTIME][0] if clt: # Rollback in order to cover different timezones # The largest difference between the time zones of two countries is # 26 hours between the Howland Islands and the Line Islands. clt -= 26 * 3600 # Check if a package contains a changelog entry that is suspiciously too far behind if clt < oldest_changelog_timestamp: self.output.add_info('W', pkg, 'changelog-time-overflow', time.strftime('%Y-%m-%d', time.gmtime(clt))) # Check if a package contians a entry in %changelog # with timestamp thats in the future of its writing elif clt > time.time(): self.output.add_info('E', pkg, 'changelog-time-in-future', time.strftime('%Y-%m-%d', time.gmtime(clt))) def _check_license(self, pkg, rpm_license): """Trigger check no-license, invalid-license-exception, invalid-license Checks are triggered due to the configuration set by the user in the configdefaults.toml Args: rpm_license: Find License: tag in the rpm package Returns: Output info to STDOUT """ def split_license(text): return (x.strip() for x in (i for i in license_regex.split(text) if i)) def split_license_exception(text): x, y = license_exception_regex.split(text)[1:3] or (text, '') return x.strip(), y.strip() # Check if a package spec file conatins a License: tag if not rpm_license: self.output.add_info('E', pkg, 'no-license') else: valid_license = True if rpm_license not in self.valid_licenses: license_string = rpm_license l1, lexception = split_license_exception(rpm_license) # SPDX allows " WITH " if lexception: license_string = l1 # Check if a package contains 'with ' license exception if lexception not in self.valid_license_exceptions: self.output.add_info('W', pkg, 'invalid-license-exception', lexception) valid_license = False for l1 in split_license(license_string): if l1 in self.valid_licenses: continue for l2 in split_license(l1): # Check if a package has a License: value other than ValidLicenses if l2 not in self.valid_licenses: self.output.add_info('W', pkg, 'invalid-license', l2) valid_license = False if not valid_license: self._unexpanded_macros(pkg, 'License', rpm_license) def _check_url(self, pkg): """Trigger check invalid-url, no-url-tag """ for tag in ('URL', 'DistURL', 'BugURL'): if hasattr(rpm, 'RPMTAG_{}'.format(tag.upper())): url = byte_to_string(pkg[getattr(rpm, 'RPMTAG_{}'.format(tag.upper()))]) self._unexpanded_macros(pkg, tag, url, is_url=True) if url: (scheme, netloc) = urlparse(url)[0:2] # Check if a package contains a unreasonable URL # [This check is also triggered with Source: tag value] if not scheme or not netloc or '.' not in netloc or \ scheme not in ('http', 'https', 'ftp') or \ (self.config.configuration['InvalidURL'] and self.invalid_url_regex.search(url)): self.output.add_info('W', pkg, 'invalid-url', tag, url) # Check if a package does not have a URL: tag in its spec file elif tag == 'URL': self.output.add_info('W', pkg, 'no-url-tag') def _check_obsolete_not_provided(self, pkg, prov_names): """Check if a package has the obsoleted package still provided in spec file to avoid dependency breakage Args: prov_names: Find the value of Provides: tag in specfile Returns: Output info to STDOUT """ obs_names = [x[0] for x in pkg.obsoletes] for dep_token in (x for x in obs_names if x not in prov_names): self.output.add_info('W', pkg, 'obsolete-not-provided', dep_token) def _check_useless_provides(self, pkg, prov_names): """Trigger check useless-provides Check if a package has a multiple number of Provides: of the same dependency example: Provides: foo Provides: foo = 1.0 Returns: Output info to STDOUT """ # TODO: should take versions, <, <=, =, >=, > into account here # https://bugzilla.redhat.com/460872 useless_provides = set() for prov in prov_names: if (prov_names.count(prov) != 1 and not prov.startswith('debuginfo(') and prov not in useless_provides): useless_provides.add(prov) for prov in sorted(useless_provides): self.output.add_info('E', pkg, 'useless-provides', prov) def _check_forbidden_controlchar(self, pkg): """Trigger check forbidden-controlchar-found Check if package contains a forbidden_words or character in tags: Provides, Conflicts, Obsoletes, Supplements, Suggests, Enhances, Recommends and Requires Returns: Output info to STDOUT """ for tagname, items in ( ('Provides', pkg.provides), ('Conflicts', pkg.conflicts), ('Obsoletes', pkg.obsoletes), ('Supplements', pkg.supplements), ('Suggests', pkg.suggests), ('Enhances', pkg.enhances), ('Recommends', pkg.recommends)): for item in items: dep = Pkg.has_forbidden_controlchars(item) if dep: self.output.add_info('E', pkg, 'forbidden-controlchar-found', '{}: {}'.format(tagname, dep)) value = Pkg.formatRequire(*item) self._unexpanded_macros(pkg, '{} {}'.format(tagname, value), value) # Check if a package contains forbidden-controlchar in Requires: tag. for pkg_token in (pkg.requires): dep = Pkg.has_forbidden_controlchars(pkg_token) if dep: self.output.add_info('E', pkg, 'forbidden-controlchar-found', 'Requires: {}'.format(dep)) def _check_self_obsoletion(self, pkg): """Trigger check self-obsoletion Check if a package does not obsoletes itself example: Name: lib-devel and Obsoletes: lib-devel in its spec file Returns: Output info to STDOUT """ obss = pkg.obsoletes if obss: provs = pkg.provides for prov in provs: for obs in obss: if Pkg.rangeCompare(obs, prov): self.output.add_info('W', pkg, 'self-obsoletion', '{} obsoletes {}'.format(Pkg.formatRequire(*obs), Pkg.formatRequire(*prov))) def _check_non_coherent_filename(self, pkg): """Trigger check in non-coherent-filename Check if a package has a named --..rpm in this order Returns: Output info STDOUT """ expfmt = rpm.expandMacro('%{_build_name_fmt}') if pkg.is_source: # _build_name_fmt often (always?) ends up not outputting src/nosrc # as arch for source packages, do it ourselves expfmt = re.sub(r'(?i)%\{?ARCH\b\}?', pkg.arch, expfmt) expected = pkg.header.sprintf(expfmt).split('/')[-1] basename = Path(pkg.filename).name if basename != expected: self.output.add_info('W', pkg, 'non-coherent-filename', basename, expected) rpmlint-2.2.0+ds1/rpmlint/checks/TmpFilesCheck.py000066400000000000000000000067621415540642600216650ustar00rootroot00000000000000from pathlib import Path import re import stat import rpm from rpmlint.checks.AbstractCheck import AbstractCheck class TmpFilesCheck(AbstractCheck): """ Validate that temporary files meet tmpfiles.d packaging rules. """ # interesting types in tmpfiles.d configuration file (see tmpfiles.d(5)) interesting_types = ('f', 'F', 'w', 'd', 'D', 'p', 'L', 'c', 'b') def check(self, pkg): if pkg.is_source: return for fname, pkgfile in pkg.files.items(): if not fname.startswith('/usr/lib/tmpfiles.d/'): continue if not stat.S_ISREG(pkgfile.mode): self.output.add_info('W', pkg, 'tmpfile-not-regular-file', fname) continue if pkgfile.is_ghost: continue self._check_pre_tmpfile(fname, pkg) self._check_post_tmpfile(fname, pkg) self._check_tmpfile_in_filelist(pkgfile, pkg) def _check_pre_tmpfile(self, fname, pkg): """ Check if the %pre section doesn't contain 'systemd-tmpfiles --create' call. Print a warning if there is systemd-tmpfiles call in the %pre section. """ pre = pkg[rpm.RPMTAG_PREIN] basename = Path(fname).name tmpfiles_regex = re.compile(r'systemd-tmpfiles --create .*%s' % re.escape(basename)) if pre and tmpfiles_regex.search(pre): self.output.add_info('W', pkg, 'pre-with-tmpfile-creation', fname) def _check_post_tmpfile(self, fname, pkg): """ Check if the %post section contains 'systemd-tmpfiles --create' call. Print a warning if there is no such call in the %post section. """ post = pkg[rpm.RPMTAG_POSTIN] basename = Path(fname).name tmpfiles_regex = re.compile(r'systemd-tmpfiles --create .*%s' % re.escape(basename)) if post and tmpfiles_regex.search(post): return self.output.add_info('W', pkg, 'post-without-tmpfile-creation', fname) def _check_tmpfile_in_filelist(self, pkgfile, pkg): """ Check if the tmpfile is listed in the filelist and marked as %ghost. Please note that a tmpfile that doesn't exist during the build can't be in the filelist without %ghost directive otherwise rpm wouldn't build it. Print a 'tmpfile-not-in-filelist' warning while it's not in the filelist (and therefore not marked as %ghost). """ with open(pkgfile.path) as inputf: for line in inputf: # skip comments line = line.split('#')[0].split('\n')[0] line = line.lstrip() if not len(line): continue # the format is: # Type Path Mode UID GID Age Argument line = re.split(r'\s+', line) if len(line) < 3: continue # we only need Type and Path tmpfiles_type = line[0] tmpfiles_path = line[1] if tmpfiles_type.endswith('!'): tmpfiles_type = tmpfiles_type[:-1] if tmpfiles_type not in self.interesting_types: continue if tmpfiles_path not in pkg.files: self.output.add_info('W', pkg, 'tmpfile-not-in-filelist', tmpfiles_path) rpmlint-2.2.0+ds1/rpmlint/checks/XinetdDepCheck.py000066400000000000000000000005051415540642600220130ustar00rootroot00000000000000from rpmlint.checks.AbstractCheck import AbstractCheck class XinetdDepCheck(AbstractCheck): def check(self, pkg): if pkg.is_source: return for req in pkg.requires + pkg.prereq: if req[0] == 'xinetd': self.output.add_info('E', pkg, 'obsolete-xinetd-requirement') rpmlint-2.2.0+ds1/rpmlint/checks/ZipCheck.py000066400000000000000000000064641415540642600207030ustar00rootroot00000000000000from pathlib import Path import re from zipfile import BadZipFile, is_zipfile, ZipFile from rpmlint.checks.AbstractCheck import AbstractCheck class ZipCheck(AbstractCheck): """ Validate zip and jar files correctness. """ zip_regex = re.compile(r'\.(zip|[ewj]ar)$') jar_regex = re.compile(r'\.[ewj]ar$') def check(self, pkg): for fname, pkgfile in pkg.files.items(): path = pkgfile.path if self.zip_regex.search(fname) and Path(path).exists() and \ Path(path).is_file() and is_zipfile(path): try: with ZipFile(path, 'r') as z: # zip checks self._check_bad_crc(pkg, fname, z) self._check_compression(pkg, fname, z) # jar checks if self.jar_regex.search(fname): self._check_classpath(pkg, fname, z) self._check_jarindex(pkg, fname, z) except BadZipFile as err: self.output.add_info('E', pkg, 'unable-to-read-zip', f'{fname}: {err}') except RuntimeError as err: self.output.add_info('W', pkg, 'unable-to-read-zip', f'{fname}: {err}') def _check_bad_crc(self, pkg, fname, zipfile): """ Check CRC issues for the files in the zipfile. Print an error if there is a file in the archive that fails CRC check. """ badcrc = zipfile.testzip() if badcrc: self.output.add_info('E', pkg, 'bad-crc-in-zip', badcrc, fname) def _check_compression(self, pkg, fname, zipfile): """ Check if zip is actually compressed or not. One file with smaller size is enough. Print an error if the zipfile is not compressed. """ # check for empty archives which are valid filecount = len(zipfile.namelist()) nullcount = 0 for zinfo in zipfile.infolist(): if zinfo.file_size == 0: nullcount += 1 if zinfo.compress_size != zinfo.file_size: return # empty files only if filecount == nullcount: return self.output.add_info('E', pkg, 'uncompressed-zip', fname) def _check_classpath(self, pkg, fname, jarfile): """ Check if META-INF/MANIFEST.MF file in the jar contains a hardcoded Class-Path. Print a warning if the path is hardcoded. """ classpath_regex = re.compile(r'^\s*Class-Path\s*:', re.MULTILINE | re.IGNORECASE) # the META-INF is optional so skip if it is not present mf = 'META-INF/MANIFEST.MF' if mf not in jarfile.namelist(): return # otherwise check for the hardcoded classpath manifest = jarfile.read(mf).decode() if classpath_regex.search(manifest): self.output.add_info('W', pkg, 'class-path-in-manifest', fname) def _check_jarindex(self, pkg, fname, jarfile): """ Check if the .jar file is indexed. Print a warning if 'META-INF/INDEX.LIST' file is not present in the jarfile. """ index = 'META-INF/INDEX.LIST' if index not in jarfile.namelist(): self.output.add_info('W', pkg, 'jar-not-indexed', fname) rpmlint-2.2.0+ds1/rpmlint/checks/ZyppSyntaxCheck.py000066400000000000000000000016531415540642600223050ustar00rootroot00000000000000from rpmlint.checks.AbstractCheck import AbstractCheck class ZyppSyntaxCheck(AbstractCheck): def check(self, pkg): # We care only about the names, versions are pointless here pkg_supplements = [x.name for x in pkg.supplements] pkg_enhances = [x.name for x in pkg.enhances] pkg_recommends = [x.name for x in pkg.recommends] pkg_suggests = [x.name for x in pkg.suggests] pkg_requires = [x.name for x in pkg.requires] pkg_conflicts = [x.name for x in pkg.conflicts] keywords = pkg_supplements + pkg_enhances + pkg_recommends + pkg_suggests + pkg_requires + pkg_conflicts for keyword in keywords: if keyword.startswith('packageand('): self.output.add_info('E', pkg, 'suse-zypp-packageand', keyword) if keyword.startswith('otherproviders('): self.output.add_info('E', pkg, 'suse-zypp-otherproviders', keyword) rpmlint-2.2.0+ds1/rpmlint/cli.py000066400000000000000000000165651415540642600165150ustar00rootroot00000000000000import argparse from pathlib import Path import sys from rpmlint.helpers import print_warning from rpmlint.lint import Lint from rpmlint.rpmdiff import Rpmdiff from rpmlint.version import __version__ __copyright__ = """ Copyright (C) 2006 Mandriva Copyright (C) 2009 Red Hat, Inc. Copyright (C) 2009 Ville Skyttä Copyright (C) 2017 SUSE LINUX GmbH This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. """ def process_diff_args(argv): """ Process the passed arguments and return the result :param argv: passed arguments """ parser = argparse.ArgumentParser(prog='rpmdiff', description='Shows basic differences between two rpm packages') parser.add_argument('old_package', metavar='RPM_ORIG', type=Path, help='the old package') parser.add_argument('new_package', metavar='RPM_NEW', type=Path, help='the new package') parser.add_argument('-V', '--version', action='version', version=__version__, help='show package version and exit') parser.add_argument('-i', '--ignore', nargs='+', default=None, choices=['S', 'M', '5', 'D', 'N', 'L', 'V', 'U', 'G', 'F', 'T'], help="""file property to ignore when calculating differences. Valid values are: S (size), M (mode), 5 (checksum), D (device), N (inode), L (number of links), V (vflags), U (user), G (group), F (digest), T (time)""") parser.add_argument('-e', '--exclude', metavar='GLOB', nargs='+', default=None, help="""Paths to exclude when showing differences. Takes a glob. When absolute (starting with /) all files in a matching directory are excluded as well. When relative, files matching the pattern anywhere are excluded but not directory contents.""") # print help if there is no argument or less than the 2 mandatory ones if len(argv) < 2: parser.print_help() sys.exit(0) options = parser.parse_args(args=argv) # convert options to dict options_dict = vars(options) return options_dict def process_lint_args(argv): """ Process the passed arguments and return the result :param argv: passed arguments """ parser = argparse.ArgumentParser(prog='rpmlint', description='Check for common problems in rpm packages') parser.add_argument('rpmfile', nargs='*', type=Path, help='files to be validated by rpmlint') parser.add_argument('-V', '--version', action='version', version=__version__, help='show package version and exit') parser.add_argument('-c', '--config', type=_validate_conf_location, help='load up additional configuration data from specified path (file or directory with *.toml files)') parser.add_argument('-e', '--explain', nargs='+', default='', help='provide detailed explanation for one specific message id') parser.add_argument('-r', '--rpmlintrc', type=_is_file_path, help='load up specified rpmlintrc file') parser.add_argument('-v', '--verbose', '--info', action='store_true', help='provide detailed explanations where available') parser.add_argument('-p', '--print-config', action='store_true', help='print the settings that are in effect when using the rpmlint') parser.add_argument('-i', '--installed', nargs='+', default='', help='installed packages to be validated by rpmlint') parser.add_argument('-t', '--time-report', action='store_true', help='print time report for run checks') parser.add_argument('-T', '--profile', action='store_true', help='print cProfile report') lint_modes_parser = parser.add_mutually_exclusive_group() lint_modes_parser.add_argument('-s', '--strict', action='store_true', help='treat all messages as errors') lint_modes_parser.add_argument('-P', '--permissive', action='store_true', help='treat individual errors as non-fatal') # print help if there is no argument if len(argv) < 1: parser.print_help() sys.exit(0) options = parser.parse_args(args=argv) # make sure rpmlintrc exists if options.rpmlintrc: if not options.rpmlintrc.exists(): print_warning(f"User specified rpmlintrc '{options.rpmlintrc}' does not exist") exit(2) # validate all the rpmlfile options to be either file or folder f_path = set() invalid_path = False for item in options.rpmfile: p_path = Path() pattern = None for pos, component in enumerate(item.parts): if ('*' in component) or ('?' in component): pattern = '/'.join(item.parts[pos:]) break p_path = p_path / component p_path = list(p_path.glob(pattern)) if pattern else [p_path] for path in p_path: if not path.exists(): print_warning(f"The file or directory '{path}' does not exist") invalid_path = True f_path.update(p_path) if invalid_path: exit(2) # convert options to dict options_dict = vars(options) # use computed rpmfile options_dict['rpmfile'] = list(f_path) return options_dict def _validate_conf_location(string): """ Help validate configuration location during argument parsing. We accept either one configuration file or a directory (then it processes all *.toml files in this directory). It exits the program if location doesn't exist. Args: string: A string representing configuration path (file or directory). Returns: A list with individual paths for each configuration file found. """ config_paths = [] path = Path(string) # Exit if file or dir doesn't exist if not path.exists(): print_warning( f"File or dir with user specified configuration '{string}' does not exist") exit(2) if path.is_dir(): config_paths.extend(path.glob('*.toml')) elif path.is_file(): config_paths.append(path) return config_paths def _is_file_path(path): p = Path(path) if not p.is_file(): raise argparse.ArgumentTypeError(f'{path} is not a valid file path') return p def lint(): """ Main wrapper for lint command processing """ options = process_lint_args(sys.argv[1:]) lint = Lint(options) sys.exit(lint.run()) def diff(): """ Main wrapper for diff command processing """ options = process_diff_args(sys.argv[1:]) d = Rpmdiff(options['old_package'], options['new_package'], ignore=options['ignore'], exclude=options['exclude']) textdiff = d.textdiff() if textdiff: print(textdiff) sys.exit(int(d.differs())) rpmlint-2.2.0+ds1/rpmlint/color.py000066400000000000000000000007141415540642600170510ustar00rootroot00000000000000import sys if hasattr(sys.stdout, 'isatty') and sys.stdout.isatty(): class Color(object): """ Colors used when doing printouts with rpmlint """ Bold = '\x1b[1m' Red = '\x1b[31m' Yellow = '\x1b[33m' Reset = '\x1b[0m' else: class Color(object): """ Colors used when doing printouts with rpmlint """ Bold = '' Red = '' Yellow = '' Reset = '' rpmlint-2.2.0+ds1/rpmlint/config.py000066400000000000000000000152521415540642600172030ustar00rootroot00000000000000import os from pathlib import Path import re import sys from rpmlint.helpers import print_warning import toml from xdg.BaseDirectory import xdg_config_dirs class Config(object): """ Load and parse rpmlint configuration. Configuration files are written in toml and should be placed in one of the XDG_CONFIG_DIRS directory or passed as "config" argument directly. By default it loads configdefaults.toml and all default locations and initializes basic testing layout for the rpmlint binary. Based on the opening order 'newer' configuration takes precedence over already existing one. """ re_filter = re.compile(r'^\s*addFilter\s*\(\s*r?[\"\'](.*)[\"\']\s*\)') re_badness = re.compile(r'\s*setBadness\s*\([\'\"](.*)[\'\"],\s*[\'\"]?(\d+)[\'\"]?\)') config_defaults = Path(__file__).parent / 'configdefaults.toml' def __init__(self, config=None): """ Initialize basic options and load rpmlint configuration. Args: config: A list of paths of configuration file(s) passed by user in command line. """ # ordered list of configuration files we loaded # useful when debugging where from we got all the config options self.conf_files = [] # Configuration content parsed from the toml configuration file self.configuration = None # List of rpmlintrc filters self.rpmlintrc_filters = [] # whether to print more information or not self.info = False # whether to treat all messages as errors or not self.strict = False # whether to treat individual errors as non-fatal self.permissive = False # find configuration files and load them self.find_configs(config) self.load_config() # if loading of the configuration failed -> fall back only to defaults if not self.configuration: # reset the configs only to defaults self.conf_files = [self.config_defaults] self.load_config() def find_configs(self, config=None): """ Find and store paths to all config files. It searches for default configuration, files in XDG_CONFIG_DIRS and user defined configuration (argument "config"). All configuration file paths found are then stored in self.conf_files variable. XDG_CONFIG_DIRS contains preference-ordered set of base directories to search for configuration files. Users can override it by their own configuration file (config parameter) and then that is added too. """ # first load up the file that contains defaults self.conf_files.append(self.config_defaults) # Skip auto-loading when running under PYTEST if not os.environ.get('PYTEST_XDIST_TESTRUNUID') and not os.environ.get('CONFIG_DISABLE_AUTOLOADING'): # Then load up config directories on system for directory in reversed(xdg_config_dirs): confdir = Path(directory) / 'rpmlint' if confdir.is_dir(): # load all configs in the folders confopts = sorted(confdir.glob('*toml')) self.conf_files += confopts # As a last item load up the user configuration if config: for path in config: if path.exists(): # load this only if it really exist self.conf_files.append(path) else: print_warning(f'(none): W: error locating user requested configuration: {path}') def _merge_dictionaries(self, dest, source, override): """ Merge in place dest dictionary for values in source in recursive way. If override is set to True, override instead of merging. """ for k, v in source.items(): vdest = dest.get(k) if isinstance(vdest, dict) and isinstance(v, dict): self._merge_dictionaries(vdest, v, override) else: if isinstance(vdest, list) and not override: for item in v: if item not in vdest: vdest.append(item) else: dest[k] = v def _is_override_config(self, config_file): return '.override.' in config_file.name def _sort_config_files(self, config_file): """ Sort config files in the following order: configdefaults.toml, normal configs, *.override.* configs """ if config_file == self.config_defaults: return 0 elif not self._is_override_config(config_file): return 1 else: return 2 def load_config(self, config=None): """ Load the configuration files and append it to local dictionary. It's stored in self.configuration with the content of already loaded options. """ if config: # just add the new config at the end of the list, someone injected # config file to us for path in config: if path not in self.conf_files and path.exists(): self.conf_files.append(path) cfg = {} # sort self.conf_files as we print list of loaded configuration files self.conf_files = sorted(self.conf_files, key=self._sort_config_files) for cf in self.conf_files: try: toml_config = toml.load(cf) self._merge_dictionaries(cfg, toml_config, self._is_override_config(cf)) except toml.decoder.TomlDecodeError as terr: print_warning(f'(none): E: fatal error while parsing configuration file {cf}: {terr}') sys.exit(4) self.configuration = cfg def load_rpmlintrc(self, rpmlintrc_file): """ Load existing rpmlintrc files. Only setBadness and addFilter are processed. """ rpmlintrc_lines = rpmlintrc_file.read_text().splitlines() filters = [] for line in rpmlintrc_lines: m = self.re_filter.match(line) if m: filters.append(m.group(1)) m = self.re_badness.match(line) if m: self.configuration['Scoring'].update({m.group(1): m.group(2)}) self.configuration['Filters'] += filters self.rpmlintrc_filters = filters def print_config(self): """Print the current state of the configuration.""" if self.configuration: print(toml.dumps(self.configuration)) def set_badness(self, result, badness): """Set specific badness for some result.""" self.configuration['Scoring'][result] = badness rpmlint-2.2.0+ds1/rpmlint/configdefaults.toml000066400000000000000000000235141415540642600212560ustar00rootroot00000000000000# Enabled checks for the rpmlint to be run Checks = [ "AlternativesCheck", "AppDataCheck", "BinariesCheck", "BuildDateCheck", 'BuildRootCheck', "ConfigFilesCheck", "DBusPolicyCheck", 'DuplicatesCheck', "DocCheck", "ErlangCheck", "FHSCheck", "FilesCheck", "IconSizesCheck", "I18NCheck", "LibraryDependencyCheck", "LogrotateCheck", "MenuCheck", "MenuXDGCheck", "MixedOwnershipCheck", "PkgConfigCheck", "PostCheck", "SignatureCheck", "SourceCheck", "SpecCheck", "TagsCheck", "ZipCheck", "ZyppSyntaxCheck", ] # Various output filters, list of regexp strings eg. "E: .* no-signature" Filters = [] # List of errors that can't be filtered BlockedFilters = [] # Treshold where we should error out, by default single error is enough BadnessThreshold = -1 # When checking that various files that should be compressed are # indeed compressed, look for this filename extension CompressExtension = "bz2" # Base directory where to extract uninstalled packages while checking # Default is to use mktemp from python to provide one ExtractDir = "" # Regexp string for words that must never exist in preamble tag values ForbiddenWords = "" # Accepted non-XDG legacy icon filenames, string regexp format IconFilename = '.*\.png$' # Regexp string to disallow in various URL tags InvalidURL = "" # Whether to allow packaging kernel modules in non-kernel packages. KernelModuleRPMsOK = true # Maximum allowed line length for Summary and Description tags MaxLineLength = 79 # Regexp string with names of packages to treat as "meta" ones. MetaPackageRegexp = '^(bundle|task)-' # String regexp validating value for the Packager tag. Packager = "" # Assumed default version of Python if one cannot be determined from files PythonDefaultVersion = "" # Trick in perl version handling PerlVersionTrick = true # Regexp string with expected suffix in Release tags. ReleaseExtension = "" # Regexp string with allowed Group tag for games RpmGamesGroup = "Games" # Doc files to which end of line and UTF-8 checks should not be applied SkipDocsRegexp = '\.(?:rtf|x?html?|svg|ml[ily]?)$' # Whether to use the Enchant spell checker for spell checking UseEnchant = true # Whether debug sources are expected to be in separate packages from # -debuginfo, typically -debugsource. UseDebugSource = true # Whether an explicit Epoch should always be specified in preamble UseEpoch = false # Whether to want default start/stop runlevels specified in init scripts UseDefaultRunlevels = true # Whether symlinks between directories should be relative. UseRelativeSymlinks = true # Whether %changelog entries should contain a version UseVersionInChangelog = true # Whether init scripts must use /var/lock/subsys UseVarLockSubsys = true # Regexp string with value for the BuildHost preamble tag ValidBuildHost = '' # Standard "needs" values for non-XDG legacy menu items ExtraMenuNeeds = [ "gnome", "icewm", "kde", "wmaker", ] # Regexp string with exceptions for hardcoded library paths. HardcodedLibPathExceptions = '/lib/(modules|cpp|perl5|rpm|hotplug|firmware|systemd)($|[\s/,])' # Values for non-XDG legacy menu item sections ValidMenuSections = [ "Office/Accessories", "Office/Address Books", "Office/Communications/Fax", "Office/Communications/PDA", "Office/Communications/Phone", "Office/Communications/Other", "Office/Drawing", "Office/Graphs", "Office/Presentations", "Office/Publishing", "Office/Spreadsheets", "Office/Tasks Management", "Office/Time Management", "Office/Wordprocessors", "Office/Other", "Internet/Chat", "Internet/File Transfer", "Internet/Instant Messaging", "Internet/Mail", "Internet/News", "Internet/Remote Access", "Internet/Video Conference", "Internet/Web Browsers", "Internet/Web Editors", "Internet/Other", "Multimedia/Graphics", "Multimedia/Sound", "Multimedia/Video", "Multimedia/Other", "System/Archiving/Backup", "System/Archiving/CD Burning", "System/Archiving/Compression", "System/Archiving/Other", "System/Configuration/Boot and Init", "System/Configuration/GNOME", "System/Configuration/Hardware", "System/Configuration/KDE", "System/Configuration/Networking", "System/Configuration/Packaging", "System/Configuration/Printing", "System/Configuration/Users", "System/Configuration/Other", "System/File Tools", "System/Monitoring", "System/Session/Windowmanagers", "System/Terminals", "System/Text Tools", "System/Other", "More Applications/Accessibility", "More Applications/Communications", "More Applications/Databases", "More Applications/Development/Code Generators", "More Applications/Development/Development Environments", "More Applications/Development/Interpreters", "More Applications/Development/Tools", "More Applications/Development/Other", "More Applications/Documentation", "More Applications/Editors", "More Applications/Education/Economy", "More Applications/Education/Geography", "More Applications/Education/History", "More Applications/Education/Languages", "More Applications/Education/Literature", "More Applications/Education/Sciences", "More Applications/Education/Sports", "More Applications/Education/Other", "More Applications/Emulators", "More Applications/Finances", "More Applications/Games/Adventure", "More Applications/Games/Arcade", "More Applications/Games/Boards", "More Applications/Games/Cards", "More Applications/Games/Puzzles", "More Applications/Games/Sports", "More Applications/Games/Strategy", "More Applications/Games/Toys", "More Applications/Games/Other", "More Applications/Sciences/Artificial Intelligence", "More Applications/Sciences/Astronomy", "More Applications/Sciences/Biology", "More Applications/Sciences/Chemistry", "More Applications/Sciences/Computer Science", "More Applications/Sciences/Data visualization", "More Applications/Sciences/Electricity", "More Applications/Sciences/Geosciences", "More Applications/Sciences/Image Processing", "More Applications/Sciences/Mathematics", "More Applications/Sciences/Numerical Analysis", "More Applications/Sciences/Parallel Computing", "More Applications/Sciences/Physics", "More Applications/Sciences/Robotics", "More Applications/Sciences/Other", "More Applications/Other", ] # Disallowed Runtime dependencies InvalidRequires = [ '^is$', '^not$', '^owned$', '^by$', '^any$', '^package$', '^libsafe\.so\.', ] # List of directory prefixes that are not allowed in packages # In addition rpmlint will warn about non-ghost files in "/run/" DisallowedDirs = [ "/home", "/mnt", "/opt", "/proc", "/tmp", "/usr/local", "/usr/tmp", "/var/local", "/var/lock", "/var/run", "/var/tmp", ] # Standard OS groups StandardGroups = [ "root", "bin", "daemon", "adm", "lp", "sync", "shutdown", "halt", "mail", "news", "uucp", "man", "nobody", ] # Standard OS users StandardUsers = [ "root", "bin", "daemon", "adm", "lp", "sync", "shutdown", "halt", "mail", "news", "uucp", "operator", "man", "nobody", ] # List of directories considered to be system default library search paths. SystemLibPaths = [ "/lib", "/usr/lib", "/usr/X11R6/lib", "/lib64", "/usr/lib64", "/usr/X11R6/lib64", ] # List of regexp strings with executables that must be compiled as position independent PieExecutables = [] # Architecture dependent paths in which packages are allowed to install files # even if they are all non-binary UsrLibBinaryException = '^/usr/lib(64)?/(perl|python|ruby|menu|pkgconfig|ocaml|lib[^/]+\.(so|l?a)$|bonobo/servers/|\.build-id|firmware)' # List of compilation flags that are mandatory MandatoryOptflags = [] # List of forbidden compilation flags ForbiddenOptflags = [] # Interpreters whose scriptlets are allowed to be empty ValidEmptyShells = [ "/sbin/ldconfig", ] # Package scriptlet interpreters ValidShells = [ "", "/bin/sh", "/bin/bash", "/sbin/sash", "/usr/bin/perl", "/sbin/ldconfig", ] # Values for the Group tag, if not specified pulled from RPM ValidGroups = [] # Permissions for files in source packages ValidSrcPerms = [ "0o644", "0o755", ] # Valid values for the License tag ValidLicenses = [] # Default valid license exceptions ValidLicenseExceptions = [] # Default list of authorized PAM modules PAMAuthorizedModules = [] # Additional warnings on specific function calls [WarnOnFunction] #[WarnOnFunction.testname] #f_name = "" #good_param = "" #description = "" # Set badness tweaking for various options [Scoring] #function=0 # Paths in which non-XDG legacy icons should be installed [IconPath] [IconPath."/usr/share/icons/"] path = "/usr/share/icons/" type = "normal" [IconPath."/usr/share/icons/mini"] path = "/usr/share/icons/mini" type = "mini" [IconPath."/usr/share/icons/large"] path = "/usr/share/icons/large" type = "large" # Default known application launchers for desktop entries [MenuLaunchers] [MenuLaunchers.kdesu] regexp = '(?:/usr/bin/)?kdesu' binaries = ['/usr/bin/kdesu', 'kdesu'] [MenuLaunchers.x11_clanapp] regexp = '(?:/usr/bin/)?launch_x11_clanapp' binaries = ['/usr/bin/launch_x11_clanapp', 'clanlib', 'libclanlib0'] [MenuLaunchers.soundwrapper] regexp = '(?:/usr/bin/)?soundwrapper' binaries = false # Exception list for dangling symlink checks. The first in each pair ("path") # is a regexp, and the second ("name") the package in which the target of the # dangling symlink is shipped [DanglingSymlinkExceptions] # A table with key/value pairs representing new descriptions for rpmlint errors [Descriptions] # rpmlint-error-name = """ # A new description for this rpmlint error # """" rpmlint-2.2.0+ds1/rpmlint/descriptions/000077500000000000000000000000001415540642600200655ustar00rootroot00000000000000rpmlint-2.2.0+ds1/rpmlint/descriptions/AlternativesCheck.toml000066400000000000000000000045741415540642600243730ustar00rootroot00000000000000alternative-generic-name-not-symlink=""" The update-alternative generic-name is not a symlink pointing to %{_sysconfdir}/alternatives/$(basename generic-name). """ alternative-link-not-ghost=""" The %{_sysconfdir}/alternatives/$(basename generic-name) link exists but is not marked as ghost. Mark it as %ghost. """ alternative-link-missing=""" The file %{_sysconfdir}/alternatives/$(basename generic-name) is missing in the file list. Mark it as %ghost and add it to the file list. """ alternative-generic-name-missing=""" The update-alternatives generic name is not in the filelist. Create it as a symlink to %{_sysconfdir}/alternatives/$(basename generic-name) and add it to the file list. """ update-alternatives-requirement-missing=""" The package does not have update-alternatives in Requires(post) or Requires(postun). This is needed for the proper scriptlet execution. """ update-alternatives-post-call-missing=""" The package does not call update-alternatives --install in post phase to install all the configuration. """ update-alternatives-postun-call-missing=""" The package does not call update-alternatives --remove in postun phase to remove all the configuration for each individual --install binary that was done in postun. """ alts-requirement-missed=""" The package does not require package alts, needed for libalternatives. """ empty-libalternatives-directory=""" The directory /usr/share/libalternatives/ has no configuration file. """ libalternatives-directory-not-exists=""" The directory /usr/share/libalternatives/ has not been defined. """ libalternatives-conf-not-found=""" Not found libalternatives configuration file, defined in the file package section. This does not have to be an error if the file has been tagged as a ghost file. """ multiple-entries=""" Libalternatives configuration file has multiple entries for that key. """ wrong-entry-format=""" The libalternatives configuration file has a wrong entry format (key=value). """ binary-entry-value-not-found=""" The libalternatives configuration file has wrong value in binary entry. """ man-entry-value-not-found=""" The value of the man entry in libalternatives configuration file has no corresponding package file entry. """ wrong-or-missed-binary-entry=""" Binary entry in libalternatives configuration file has no corresponding package file entry. """ wrong-tag-found=""" Unknown tag in libalternatives configuration file. """rpmlint-2.2.0+ds1/rpmlint/descriptions/AppDataCheck.toml000066400000000000000000000001421415540642600232270ustar00rootroot00000000000000invalid-appdata-file = """ Appdata file is not valid. Check the validity with appstream-util. """ rpmlint-2.2.0+ds1/rpmlint/descriptions/BashismsCheck.toml000066400000000000000000000005471415540642600234770ustar00rootroot00000000000000bin-sh-syntax-error=""" A /bin/sh shell script contains a POSIX shell syntax error. This might indicate a potential bash-specific feature being used, try dash -n for more detailed error message.""" potential-bashisms=""" checkbashisms reported potential bashisms in a /bin/sh shell script, you might want to manually check this script for bashisms.""" rpmlint-2.2.0+ds1/rpmlint/descriptions/BinariesCheck.toml000066400000000000000000000151501415540642600234560ustar00rootroot00000000000000arch-independent-package-contains-binary-or-object = """ The package is marked as noarch and contains a binary or object file. """ arch-dependent-file-in-usr-share=""" This package installs an ELF binary in the /usr/share hierarchy, which is reserved for architecture-independent files only. """ binary-in-etc=""" This package installs an ELF binary in /etc. """ noarch-with-lib64=""" This package is marked as noarch but installs files into lib64. Not all architectures have this in path, so the package can't be noarch. """ no-soname=""" The library has no soname. """ invalid-soname=""" The soname of the library is neither of the form lib.so. or lib-.so. """ invalid-ldconfig-symlink=""" The symbolic link references the wrong file. It should reference the shared library. """ no-ldconfig-symlink=""" The package should not only include the shared library itself, but also the symbolic link which ldconfig would produce. (This is necessary, so that the link gets removed by rpm automatically when the package gets removed). """ shlib-with-non-pic-code=""" The listed shared libraries contain object code that was compiled without -fPIC. All object code in shared libraries should be recompiled separately from the static libraries with the -fPIC option. Use the ``eu-findtextrel'' command on a library with debugging symbols to list code compiled without -fPIC. Another common mistake that causes this problem is linking with ``gcc -Wl,-shared'' instead of ``gcc -shared''. """ libtool-wrapper-in-package=""" The package contains a libtool wrapper shell script. Instead of installing the libtool wrapper file run ``libtool --mode=install install -m perm '' in order to install the relinked file. """ binary-or-shlib-defines-rpath=""" The binary or shared library defines `RPATH'. """ statically-linked-binary=""" The package installs a statically linked binary or object file. """ executable-in-library-package=""" The package mixes up libraries and executables. Mixing up these both types of files makes upgrades quite impossible. """ non-versioned-file-in-library-package=""" The package contains files in non versioned directories. This makes it impossible to have multiple major versions of the libraries installed. One solution can be to change the directories which contain the files to subdirs of /usr/lib/- or /usr/share/-. Another solution can be to include a version number in the file names themselves. """ shlib-policy-name-error=""" The package contains shared library but is not named after its SONAME. """ invalid-la-file=""" This .la file contains a reference to /tmp or /home. """ no-binary=""" The package should be of the noarch architecture because it doesn't contain any binaries. """ undefined-non-weak-symbol=""" The binary contains undefined non-weak symbols. """ unused-direct-shlib-dependency=""" The binary contains unused direct shared library dependencies. This may indicate gratuitously bloated linkage; check that the binary has been linked with the intended shared libraries only. """ only-non-binary-in-usr-lib=""" There are only non binary files in /usr/lib so they should be in /usr/share. """ readelf-failed=""" Executing readelf on this file failed, all checks could not be run. """ ldd-failed=""" Executing ldd on this file failed, all checks could not be run. """ strings-failed=""" Executing strings on this file failed, all checks could not be run. """ objdump-failed=""" Executing objdump on this file failed, all checks could not be run. """ executable-stack=""" The binary declares the stack as executable. Executable stack is usually an error as it is only needed if the code contains GCC trampolines or similar constructs which uses code on the stack. One common source for needlessly executable stack cases are object files built from assembler files which don't define a proper .note.GNU-stack section. """ missing-PT_GNU_STACK-section=""" The binary lacks a PT_GNU_STACK section. This forces the dynamic linker to make the stack executable. """ non-position-independent-executable=""" This executable must be position independent. Check that it is built with -fPIE/-fpie in compiler flags and -pie in linker flags. """ position-independent-executable-suggested=""" This executable should be position independent (all binaries should). Check that it is built with -fPIE/-fpie in compiler flags and -pie in linker flags. """ missing-call-to-setgroups-before-setuid=""" This executable is calling setuid and setgid without setgroups or initgroups. This means it didn't relinquish all groups, and this would be a potential security issue. """ call-to-mktemp=""" This executable calls mktemp. As advised by the manpage (mktemp(3)), this function should be avoided. """ unstripped-binary-or-object=""" This executable should be stripped from debugging symbols, in order to take less space and be loaded faster. This is usually done automatically at buildtime by rpm. """ lto-bytecode=""" This executable contains a LTO section. LTO bytecode is not portable and should not be distributed in static libraries or e.g. Python modules. """ lto-no-text-in-archive=""" This archive does not contain a non-empty .text section. The archive was not created with -ffat-lto-objects option. """ linked-against-opt-library=""" This executable is linked against a shared library in /opt folder. """ linked-against-usr-library=""" Libraries and executables under /bin, /sbin, /lib and /lib64 may not link against a shared library in /usr folder. """ static-library-without-symtab=""" The static library doesn't contain any symbols and therefore can't be linked against. This may indicate a stripped archive. """ binary-or-shlib-calls-gethostbyname=""" The binary calls gethostbyname. Please port the code to use getaddrinfo. """ static-library-without-debuginfo=""" The static library doesn't contain any debuginfo. Binaries linking against this static library can't be properly debugged. """ shared-library-not-executable=""" This library doesn't have the executable bit set. Without this bit set, rpm for instance won't be able identify the file as a library and not generate dependencies or strip debug symbols from it. """ library-not-linked-against-libc=""" The library is not dynamically linked against libc. """ program-not-linked-against-libc=""" The binary is not dynamically linked against libc. """ shared-library-without-dependency-information=""" The listed shared library doesn't include information about which other libraries the library was linked against. """ missing-mandatory-optflags=""" This executable was not compiled with expected flags. """ forbidden-optflags=""" This executable was compiled with an unexpected flag. """ rpmlint-2.2.0+ds1/rpmlint/descriptions/BuildDateCheck.toml000066400000000000000000000003671415540642600235630ustar00rootroot00000000000000file-contains-current-date=""" Your file contains the current date, this may cause the package to rebuild in excess. """ file-contains-date-and-time=""" Your file uses __DATE__ and __TIME__ which causes the package to rebuild when not needed. """ rpmlint-2.2.0+ds1/rpmlint/descriptions/BuildRootCheck.toml000066400000000000000000000001121415540642600236150ustar00rootroot00000000000000file-contains-buildroot=""" Your file contains traces of %{buildroot}.""" rpmlint-2.2.0+ds1/rpmlint/descriptions/CheckForXinetd.toml000066400000000000000000000002441415540642600236220ustar00rootroot00000000000000obsolete-xinetd-requirement=""" Xinetd is obsolete by systemd socket activated services. Please stop using xinetd and switch to socket activation from systemd. """ rpmlint-2.2.0+ds1/rpmlint/descriptions/ConfigFilesCheck.toml000066400000000000000000000007711415540642600241150ustar00rootroot00000000000000non-etc-or-var-file-marked-as-conffile=""" A file not in /etc or /var is marked as being a configuration file (%config). Please put your configuration files in /etc or /var. """ conffile-without-noreplace-flag=""" A configuration file is stored in your package without the noreplace flag. This flag tells RPM not to overwrite or replace a configuration file to protect local modifications. A way to resolve this is to put the following in your SPEC file: %config(noreplace) /etc/your_config_file_here """ rpmlint-2.2.0+ds1/rpmlint/descriptions/DBusPolicyCheck.toml000066400000000000000000000012761415540642600237430ustar00rootroot00000000000000dbus-policy-allow-without-destination=""" 'allow' directives must always specify a 'send_destination'.""" dbus-policy-allow-receive=""" allow receive_* is normally not needed as that is the default.""" dbus-policy-deny-without-destination=""" 'deny' directives must always specify a 'send_destination' otherwise messages to other services could be blocked.""" dbus-policy-missing-allow=""" Every dbus config normally needs a line of the form or similar. If that is missing the service will not work with a dbus that uses deny as default policy""" dbus-parsing-exception=""" A python exception was raised which prevents further analysis of the DBus rule file.""" rpmlint-2.2.0+ds1/rpmlint/descriptions/DocCheck.toml000066400000000000000000000011641415540642600224270ustar00rootroot00000000000000executable-docs=""" Documentation should not be executable. """ doc-file-dependency=""" A file marked as %doc creates a possible additional dependency in the package. This is not wanted and may be caused by example scripts with executable bits set included in the package's documentation. """ install-file-in-docs=""" A file whose name suggests that it contains installation instructions is included in the package. Such instructions are often not relevant for already installed packages. """ package-with-huge-docs=""" More than half the size of your package is documentation. Consider splitting it into a -doc subpackage. """ rpmlint-2.2.0+ds1/rpmlint/descriptions/DuplicatesCheck.toml000066400000000000000000000013061415540642600240150ustar00rootroot00000000000000files-duplicated-waste=""" Your package contains duplicated files that are not hard- or symlinks. You should use the %fdupes macro to link the files to one. """ hardlink-across-partition=""" Your package contains two files that are apparently hardlinked and that are likely on different partitions. Installation of such an RPM will fail due to RPM being unable to unpack the hardlink. Do not hardlink across the first two levels of a path, e.g. between /var/ftp and /var/www or /etc and /usr. """ hardlink-across-config-files=""" Your package contains two config files that are apparently hardlinked. Hardlinking a config file is probably not what you want. Please double check and report false positives. """ rpmlint-2.2.0+ds1/rpmlint/descriptions/ErlangCheck.toml000066400000000000000000000007671415540642600231420ustar00rootroot00000000000000beam-compile-info-missed=""" Your beam file has missed compile info chunk. """ beam-compiled-without-debuginfo=""" Your beam file indicates that it doesn't contain debug_info. Please, make sure that you compile with +debug_info. """ beam-was-not-recompiled=""" It seems that your beam file was not compiled by you, but was just copied in binary form to destination. Please, make sure that you really compile it from the sources. """ pybeam-failed=""" Invocation of the Python pybeam library failed. """ rpmlint-2.2.0+ds1/rpmlint/descriptions/FilesCheck.toml000066400000000000000000000373051415540642600227720ustar00rootroot00000000000000no-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. """ library-without-ldconfig-postin=""" This package contains a library and provides no %post scriptlet containing a call to ldconfig. """ postin-without-ldconfig=""" This package contains a library and its %post scriptlet doesn't call ldconfig. """ library-without-ldconfig-postun=""" This package contains a library and provides no %postun scriptlet containing 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 scriptlet containing 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 scriptlet containing 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. """ non-ghost-in-run=""" A file or directory in the package is located in /run. Files installed in this directory should be marked as %ghost and created at runtime to work properly in tmpfs /run setups. """ systemd-unit-in-etc=""" A systemd unit has been packaged in /etc/systemd/system. These units should be installed in the system unit dir instead. """ udev-rule-in-etc=""" A udev rule has been packaged in /etc/udev/rules.d. These rules should be installed in the system rules dir instead. """ tmpfiles-conf-in-etc=""" A tmpfiles config has been packaged in /etc/tmpfiles.d. These rules should be installed in the system tmpfiles dir instead. """ subdir-in-bin=""" The package contains a subdirectory in /usr/bin. It's not permitted to create a subdir there. Create it in /usr/lib/ instead. """ backup-file-in-package=""" You have a file whose name looks like one for backup files, usually created by an editor or resulting from applying unclean (fuzzy, or ones with line offsets) patches. """ version-control-internal-file=""" You have included file(s) internally used by a version control system in the package. Move these files out of the package and rebuild it. """ htaccess-file=""" You have individual apache configuration .htaccess file(s) in your package. Replace them by a central configuration file in /etc/, according to the web application packaging policy for your distribution. """ info-dir-file=""" You have /usr/info/dir or /usr/share/info/dir in your package. It will cause conflicts with other packages and thus is not allowed. Please remove it and rebuild your package. """ non-conffile-in-etc=""" A non-executable file in your package is being installed in /etc, but is not a configuration file. All non-executable files in /etc should be configuration files. Mark the file as %config in the spec file. """ compressed-symlink-with-wrong-ext=""" The symlink points to a compressed file but doesn't use the same extension. """ setuid-binary=""" The file is setuid; this may be dangerous, especially if this file is setuid root. Sometimes file capabilities can be used instead of setuid bits. """ setgid-binary=""" The file is setgid. Usually this is a packaging bug. If this is a game, then, you should use the proper rpm group, or location. """ non-standard-executable-perm=""" A standard executable should have permission set to 0755. If you get this message, it means that you have a wrong executable permissions in some files included 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 file that is needed only e.g. when developing or building software is included in a non-devel package. These files should go in devel packages. """ non-devel-file-in-devel-package=""" A non-development file is located in a devel package. """ non-standard-dir-perm=""" A standard directory should have permission set to 0755. If you get this message, it means that you have wrong directory permissions in some dirs included in your package. """ spurious-executable-perm=""" The file is installed with executable permissions, but was identified as one that probably should not be executable. Verify if the executable bits are desired, and remove if not. """ world-writable=""" A file or directory in the package is installed with world writable permissions, which is most likely a security issue. """ standard-dir-owned-by-package=""" This package owns a directory that is part of the standard hierarchy, which can lead to default directory permissions or ownerships being changed to something non-standard. """ cross-directory-hard-link=""" File is hard linked across directories. This can cause problems in installations where the directories are located on different devices. """ dangling-symlink=""" The target of the symbolic link does not exist within this package or its file based dependencies. Verify spelling of the link target and that the target is included in a package in this package's dependency chain. """ symlink-should-be-relative=""" Absolute symlinks are problematic eg. when working with chroot environments. symlinks(8) is a tool that can be useful for creating/dealing with relative symlinks at package build time. """ dangling-relative-symlink=""" The target of the symbolic link does not exist within this package or its file based dependencies. Verify spelling of the link target and that the target is included in a package in this package's dependency chain. """ non-readable=""" The file can't be read by everybody. Review if this is expected. """ incoherent-logrotate-file=""" Your logrotate file should be named /etc/logrotate.d/. """ non-root-user-log-file=""" If you need log files owned by a non-root user, just create a subdir in /var/log and put your log files in it. """ non-root-group-log-file=""" If you need log files owned by a non-root group, just create a subdir in /var/log and put your log files in it. """ 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 from the package if not. """ module-without-depmod-postin=""" This package contains a kernel module but provides no call to depmod in the %post scriptlet. """ postin-with-wrong-depmod=""" This package contains a kernel module but its %post scriptlet calls depmod for the wrong kernel. """ module-without-depmod-postun=""" This package contains a kernel module but provides no call to depmod in the %postun scriptlet. """ postun-with-wrong-depmod=""" This package contains a kernel module but its %postun scriptlet calls depmod for the wrong kernel. """ log-files-without-logrotate=""" This package contains files in /var/log/ without adding logrotate configuration for them. """ unexpanded-macro=""" This package contains a file whose path contains something that looks like an unexpanded macro; this is often the sign of a misspelling. Please check your specfile. """ manifest-in-perl-module=""" This perl module package contains a MANIFEST or a MANIFEST.SKIP file in the documentation directory. """ siteperl-in-perl-module=""" This perl module package installs files under the subdirectory site_perl, while they must appear under vendor_perl. """ executable-marked-as-config-file=""" Executables must not be marked as config files because that may prevent upgrades from working correctly. If you need to be able to customize an executable, make it for example read a config file in /etc/sysconfig. """ sourced-script-with-shebang=""" This text file contains a shebang, but is meant to be sourced, not executed. """ executable-sourced-script=""" This text file has executable bit set, but is meant to be sourced, not executed. """ wrong-script-interpreter=""" This script uses an interpreter which is either an inappropriate one or located in an inappropriate directory for packaged system software. Alternatively, if the file should not be executed, then ensure that it is not marked as executable. """ env-script-interpreter=""" This script uses 'env' as an interpreter. For the rpm runtime dependency detection to work, the shebang #!/usr/bin/env needs to be patched into #!/usr/bin/ otherwise the package dependency generator merely adds a dependency on /usr/bin/env rather than the actual interpreter /usr/bin/. Alternatively, if the file should not be executed, then ensure that it is not marked as executable or don't install it in a path that is reserved for executables. """ non-executable-script=""" This text file contains a shebang or is located in a path dedicated for executables, but lacks the executable bits and cannot thus be executed. If the file is meant to be an executable script, add the executable bits, otherwise remove the shebang or move the file elsewhere. """ script-without-shebang=""" This text file has executable bits set or is located in a path dedicated for executables, but lacks a shebang and cannot thus be executed. If the file is meant to be an executable script, add the shebang, otherwise remove the executable bits or move the file elsewhere. """ wrong-script-end-of-line-encoding=""" This script has wrong end-of-line encoding, usually caused by creation or modification on a non-Unix system. It will prevent its execution. """ wrong-file-end-of-line-encoding=""" This file has wrong end-of-line encoding, usually caused by creation or modification on a non-Unix system. It could prevent it from being displayed correctly in some circumstances. """ file-not-utf8=""" The character encoding of this file is not UTF-8. Consider converting it in the specfile's %prep section for example using iconv(1). """ filename-not-utf8=""" The character encoding of the name of this file is not UTF-8. Rename it. """ file-in-meta-package=""" This package seems to be a meta-package (an empty package used to require other packages), but it is not empty. You should remove or rename it, see the option MetaPackageRegexp. """ empty-debuginfo-package=""" This debuginfo package contains no files. This is often a sign of binaries being unexpectedly stripped too early during the build, rpmbuild not being able to strip the binaries, the package actually being a noarch one but erratically packaged as arch dependent, or something else. Verify what the case is, and if there's no way to produce useful debuginfo out of it, disable creation of the debuginfo package. """ debuginfo-without-sources=""" This debuginfo package appears to contain debug symbols but no source files. This is often a sign of binaries being unexpectedly stripped too early during the build, or being compiled without compiler debug flags (which again often is a sign of distro's default compiler flags ignored which might have security consequences), or other compiler flags which result in rpmbuild's debuginfo extraction not working as expected. Verify that the binaries are not unexpectedly stripped and that the intended compiler flags are used. """ missing-dependency-to-crontabs=""" This package installs a file in /etc/cron.*/ but doesn't require crontabs to be installed. As crontabs is not part of the essential packages, your package should explicitely require crontabs to make sure that your cron job is executed. If it is an optional feature of your package, recommend or suggest crontabs. """ missing-dependency-to-logrotate=""" This package installs a file in /etc/logrotate.d/ but doesn't require logrotate to be installed. Because logrotate is not part of the essential packages, your package should explicitely depend on logrotate to make sure that your logrotate job is executed. If it is an optional feature of your package, recommend or suggest logrotate. """ missing-dependency-to-xinetd=""" This package installs a file in /etc/xinetd.d/ but doesn't require xinetd to be installed. Because xinetd is not part of the essential packages, your package should explicitely depend on logrotate to make sure that your xinetd job is executed. If it is an optional feature of your package, recommend or suggest xinetd. """ read-error=""" This file could not be read. A reason for this could be that the info about it in the rpm header indicates that it is supposed to be a readable normal file but it actually is not in the filesystem. Because of this, some checks will be skipped. """ inaccessible-filename=""" An error occurred while trying to access this file due to some characters in its name. Because of this, some checks will be skipped. Access could work with some other locale settings. """ executable-crontab-file=""" This crontab file has executable bit set, which is refused by newer version of cron """ non-owner-writeable-only-crontab-file=""" This crontab file is writeable by other users as its owner, which is refused by newer version of cron and insecure """ symlink-crontab-file=""" This crontab file is a symbolic link, which is insecure and refused by newer version of cron """ rpath-in-buildconfig=""" This build configuration file contains rpaths which will be introduced into dependent packages. """ python-bytecode-wrong-magic-value=""" The 'magic' ABI version embedded in this python bytecode file isn't equal to that of the corresponding runtime, which will force the interpreter to recompile the .py source every time, ignoring the saved bytecode. """ python-bytecode-inconsistent-mtime=""" The timestamp embedded in this python bytecode file isn't equal to the mtime of the original source file, which will force the interpreter to recompile the .py source every time, ignoring the saved bytecode. """ python-bytecode-without-source=""" This python bytecode file (.pyo/.pyc) is not accompanied by its original source file (.py) """ duplicate-executable=""" This executable file exists in more than one standard binary directories. It can cause problems when dirs in $PATH are reordered. """ no-manual-page-for-binary=""" Each executable in standard binary directories should have a man page. """ incorrect-fsf-address=""" The Free Software Foundation address in this file seems to be outdated or misspelled. Ask upstream to update the address, or if this is a license file, possibly the entire file with a new copy available from the FSF. """ gzipped-svg-icon=""" Not all desktop environments that support SVG icons support them gzipped (.svgz). Install the icon as plain uncompressed SVG. """ pem-certificate=""" Shipping a PEM certificate is likely wrong. If used for the default configuration, this is insecure ( since the certificate is public ). If this is used for validation, ie a CA certificate store, then this must be kept up to date due to CA compromise. The only valid reason is for testing purpose, so ignore this warning if this is the case. """ pem-private-key=""" Private key in a .pem file should not be shipped in a rpm, unless this is for testing purpose ( ie, run by the test suite ). Shipping it as part of the example documentation mean that someone will sooner or later use it and setup a insecure configuration. """ tcl-extension-file=""" Script libraries for Tcl extensions should be in a package-specific subdir of /usr/share/tcl. """ makefile-junk=""" Your package contains makefiles that only make sense in a source package. Did you package a complete directory from the tarball by using %doc? Consider removing Makefile* from this directory at the end of your %install section to reduce bloat. """ rpmlint-2.2.0+ds1/rpmlint/descriptions/I18NCheck.toml000066400000000000000000000002261415540642600223770ustar00rootroot00000000000000"subfile-not-in-%lang"=""" If /foo/bar is not tagged %lang(XX) whereas /foo is, the package won't be installable if XX is not in %_install_langs. """ rpmlint-2.2.0+ds1/rpmlint/descriptions/IconSizesCheck.toml000066400000000000000000000002611415540642600236250ustar00rootroot00000000000000wrong-icon-size=""" Your icon file is installed in a fixed-size directory, but has a largely incorrect size. Some desktop environments (e.g. GNOME) display them incorrectly.""" rpmlint-2.2.0+ds1/rpmlint/descriptions/InitScriptCheck.toml000066400000000000000000000060301415540642600240070ustar00rootroot00000000000000init-script-without-chkconfig-postin=""" The package contains an init script but doesn't contain a %post with a call to chkconfig. """ postin-without-chkconfig=""" The package contains an init script but doesn't call chkconfig in its %post script. """ init-script-without-chkconfig-preun=""" The package contains an init script but doesn't contain a %preun with a call to chkconfig. """ preun-without-chkconfig=""" The package contains an init script but doesn't call chkconfig in its %preun script. """ missing-lsb-keyword=""" The package contains an init script that does not contain one of the LSB init script comment block convention keywords that are recommendable for all init scripts. If there is nothing to add to a keyword's value, include the keyword in the script with an empty value. Note that as of version 3.2, the LSB specification does not mandate presence of any keywords. """ no-status-entry=""" In your init script (/etc/rc.d/init.d/your_file), you don't have a 'status' entry, which is necessary for good functionality. """ no-reload-entry=""" In your init script (/etc/rc.d/init.d/your_file), you don't have a 'reload' entry, which is necessary for good functionality. """ no-chkconfig-line=""" The init script doesn't contain a chkconfig line to specify the runlevels at which to start and stop it. """ no-default-runlevel=""" The default runlevel isn't specified in the init script. """ service-default-enabled=""" The service is enabled by default after 'chkconfig --add'; for security reasons, most services should not be. Use '-' as the default runlevel in the init script's 'chkconfig:' line and/or remove the 'Default-Start:' LSB keyword to fix this if appropriate for this service. """ subsys-unsupported=""" The init script uses /var/lock/subsys which is not supported by this distribution. """ subsys-not-used=""" While your daemon is running, you have to put a lock file in /var/lock/subsys/. To see an example, look at this directory on your machine and examine the corresponding init scripts. """ incoherent-subsys=""" The filename of your lock file in /var/lock/subsys/ is incoherent with your actual init script name. For example, if your script name is httpd, you have to use 'httpd' as the filename in your subsys directory. It is also possible that rpmlint gets this wrong, especially if the init script contains nontrivial shell variables and/or assignments. These cases usually manifest themselves when rpmlint reports that the subsys name starts a with '$'; in these cases a warning instead of an error is reported and you should check the script manually. """ incoherent-init-script-name=""" The init script name should be the same as the package name in lower case, or one with 'd' appended if it invokes a process by that name. """ init-script-name-with-dot=""" The init script name should not contain a dot in its name. Some versions of chkconfig don't work as expected with init script names like that. """ init-script-non-executable=""" The init script should have at least the execution bit set for root in order for it to run at boot time. """ rpmlint-2.2.0+ds1/rpmlint/descriptions/LSBCheck.toml000066400000000000000000000007751415540642600223510ustar00rootroot00000000000000non-lsb-compliant-package-name=""" Your package name contains an illegal character that is not LSB-compliant. Use only lowercase letters, numbers, '.', '+' or '-' characters. """ non-lsb-compliant-version=""" Your version number contains an illegal character that is not LSB-compliant. Use only alphanumeric symbols, '.' or '+' characters. """ non-lsb-compliant-release=""" Your release number contains an illegal character that is not LSB-compliant. Use only alphanumeric symbols, '.' or '+' characters. """ rpmlint-2.2.0+ds1/rpmlint/descriptions/LibraryDependencyCheck.toml000066400000000000000000000003341415540642600253230ustar00rootroot00000000000000no-library-dependency-for=""" The package misses dependency on a library package that provides the shared library. """ no-library-dependency-on=""" The package misses dependency on a package which file it links to. """ rpmlint-2.2.0+ds1/rpmlint/descriptions/LogrotateCheck.toml000066400000000000000000000006611415540642600236630ustar00rootroot00000000000000logrotate-duplicate=""" There are dupliated logrotate entries with different settings for the specified file.""" logrotate-user-writable-log-dir=""" The log directory is writable by unprivileged users. Please fix the permissions so only root can write there or add the 'su' option to your logrotate config.""" logrotate-log-dir-not-packaged=""" Please add the specified directory to the file list to be able to check permissions.""" rpmlint-2.2.0+ds1/rpmlint/descriptions/MenuCheck.toml000066400000000000000000000054561415540642600226360ustar00rootroot00000000000000non-file-in-menu-dir=""" The directory /usr/lib/menu must not contain anything else than normal files. """ non-coherent-menu-filename=""" The menu file name should be /usr/lib/menu/. """ non-readable-menu-file=""" The menu file isn't readable. Check the permissions. """ non-transparent-xpm=""" xpm icon should be transparent for use in menus. """ menu-without-postin=""" A menu file exists in the package but no %post scriptlet is present to call update-menus. """ postin-without-update-menus=""" A menu file exists in the package but its %post scriptlet doesn't call update-menus. """ menu-without-postun=""" A menu file exists in the package but no %postun scriptlet is present to call update-menus. """ postun-without-update-menus=""" A menu file exists in the package but its %postun scriptlet doesn't call update-menus. """ incoherent-package-value-in-menu=""" The package field of the menu entry isn't the same as the package name. """ use-of-launcher-in-menu-but-no-requires-on=""" The menu command uses a launcher but there is no dependency in the package that contains it. """ menu-command-not-in-package=""" The command used in the menu isn't included in the package. """ menu-longtitle-not-capitalized=""" The longtitle field of the menu doesn't start with a capital letter. """ version-in-menu-longtitle=""" The longtitle filed of the menu entry contains a version. This is bad because it will be prone to error when the version of the package changes. """ no-longtitle-in-menu=""" The longtitle field isn't present in the menu entry. """ menu-title-not-capitalized=""" The title field of the menu entry doesn't start with a capital letter. """ version-in-menu-title=""" The title filed of the menu entry contains a version. This is bad because it will be prone to error when the version of the package changes. """ no-title-in-menu=""" The title field isn't present in the menu entry. """ invalid-menu-section=""" The section field of the menu entry isn't standard. """ unable-to-parse-menu-section=""" rpmlint wasn't able to parse the menu section. Please report a bug. """ hardcoded-path-in-menu-icon=""" The path of the icon is hardcoded in the menu entry. This prevents multiple sizes of the icon from being found. """ normal-icon-not-in-package=""" The normal icon isn't present in the package. """ mini-icon-not-in-package=""" The mini icon isn't present in the package. """ large-icon-not-in-package=""" The large icon isn't present in the package. """ no-icon-in-menu=""" The menu entry doesn't contain an icon field. """ invalid-title=""" The menu title contains invalid characters like /. """ missing-menu-command=""" The menu file doesn't contain a command. """ menu-in-wrong-directory=""" The menu files must be under /usr/lib/menu. """ non-xdg-migrated-menu=""" The menu file has not been migrated to new XDG menu system. """ rpmlint-2.2.0+ds1/rpmlint/descriptions/MenuXDGCheck.toml000066400000000000000000000013271415540642600231720ustar00rootroot00000000000000invalid-desktopfile=""" The .desktop file is not valid, check with desktop-file-validate """ non-utf8-desktopfile=""" The .desktop file is not encoded in UTF-8. """ desktopfile-without-binary=""" The .desktop file is for a file not present in the package. You should check the requires or see if this is not a error. """ desktopfile-duplicate-section=""" The .desktop file contains the mentioned section name twice, which can trigger parsing ambiguities. Remove the duplicate. """ desktopfile-duplicate-option=""" The .desktop file contains the mentioned option key twice, which can trigger parsing ambiguities. Remove the duplicate. """ desktopfile-missing-header=""" The .desktop file should start with a section header. """ rpmlint-2.2.0+ds1/rpmlint/descriptions/MixedOwnershipCheck.toml000066400000000000000000000003571415540642600246720ustar00rootroot00000000000000file-parent-ownership-mismatch=""" A file or directory is stored in a directory owned by another unprivileged user. This is a security issue since the owner of the parent directory can replace this file/directory with a different one. """ rpmlint-2.2.0+ds1/rpmlint/descriptions/PAMModulesCheck.toml000066400000000000000000000002441415540642600236660ustar00rootroot00000000000000pam-unauthorized-module=""" The package installs a PAM module. If the package is intended for inclusion the PAM module name must be included in the white list. """ rpmlint-2.2.0+ds1/rpmlint/descriptions/PkgConfigCheck.toml000066400000000000000000000015061415540642600235710ustar00rootroot00000000000000invalid-pkgconfig-file=""" Your .pc file appears to be invalid. Possible causes are: - it contains traces of $RPM_BUILD_ROOT or $RPM_BUILD_DIR. - it contains unreplaced macros (@have_foo@) - it references invalid paths (e.g. /home or /tmp) """ pkgconfig-invalid-libs-dir=""" Your .pc file contains -L/usr/lib or -L/lib and is built on a lib64 target, or contains references to -L/usr/lib64 or -L/lib64 and is built for a lib target. """ double-slash-in-pkgconfig-path=""" This pkg-config file contains a path with a double slash ('//') in it. This will break debugedit when stripping debug symbols during package building if these paths have been passed to gcc, and fail with the following error: canonicalization unexpectedly shrank by one character. """ pkgconfig-exception=""" An exception during parsing of .pc file has occurred. """ rpmlint-2.2.0+ds1/rpmlint/descriptions/SharedLibraryPolicyCheck.toml000066400000000000000000000021771415540642600256420ustar00rootroot00000000000000shlib-policy-excessive-dependency=""" Your package starts with 'lib' as part of its name, but also contains binaries that have more dependencies than those that already required by the libraries. Those binaries should probably not be part of the library package, but split into a seperate one to reduce the additional dependencies for other users of this library. """ shlib-policy-missing-lib=""" Your package name looks its based on soname, but does not provide any libraries. """ shlib-fixed-dependency=""" Your shared library package requires a fixed version of another package. The intention of the Shared Library Policy is to allow parallel installation of multiple versions of the same shared library, hard dependencies likely make that impossible. Please remove this dependency and instead move it to the runtime uses of your library. """ shlib-unversioned-lib=""" Your package matches the Shared Library Policy Naming Scheme but contains an unversioned library. Therefore it is very unlikely that your package can be installed in parallel to another version of this library package. Consider moving unversioned parts into another package. """ rpmlint-2.2.0+ds1/rpmlint/descriptions/SignatureCheck.toml000066400000000000000000000004351415540642600236630ustar00rootroot00000000000000no-signature=""" You have to include your pgp or gpg signature in your package. """ unknown-key=""" The package was signed, but with an unknown key. See the rpm --import option for more information. """ invalid-signature=""" The package was signed, but the signature is corrupted. """ rpmlint-2.2.0+ds1/rpmlint/descriptions/SourceCheck.toml000066400000000000000000000007301415540642600231600ustar00rootroot00000000000000multiple-specfiles=""" Your package contains multiple spec files. To build a correct package, you need to have only one spec file containing all your RPM information. """ strange-permission=""" A file that you listed to include in your package has strange permissions. Usually, a file should have 0644 permissions. """ inconsistent-file-extension=""" The file name extension indicates a different compression format than what is actually used (as checked by file(1)). """ rpmlint-2.2.0+ds1/rpmlint/descriptions/SpecCheck.toml000066400000000000000000000204571415540642600226220ustar00rootroot00000000000000no-spec-file=""" No spec file was specified in your RPM metadata. Please specify a valid SPEC file to build a valid RPM package. """ invalid-spec-name=""" The spec file name (without the .spec suffix) must match the package name ('Name:' tag). """ non-utf8-spec-file=""" The character encoding of the spec file is not UTF-8. """ use-of-RPM_SOURCE_DIR=""" You use $RPM_SOURCE_DIR or %{_sourcedir} in your spec file. If you have to use a directory for building, use $RPM_BUILD_ROOT instead. """ patch-not-applied=""" A patch is included in your package but was not applied. """ obsolete-tag=""" The following tags are obsolete: Copyright and Serial. They must be replaced by License and Epoch respectively. """ deprecated-grep=""" Direct use of grep as egrep or fgrep is deprecated in GNU grep and historical in POSIX, use grep -E and grep -F instead. """ no-buildroot-tag=""" The BuildRoot tag isn't used in your spec. It must be used in order to allow building the package as non root on some systems. For some rpm versions (e.g. rpm.org >= 4.6) the BuildRoot tag is not necessary in specfiles and is ignored by rpmbuild; if your package is only going to be built with such rpm versions you can ignore this warning. """ hardcoded-path-in-buildroot-tag=""" A path is hardcoded in your Buildroot tag. It should be replaced by something like %{_tmppath}/%{name}-%{version}-build. """ hardcoded-packager-tag=""" The Packager tag is hardcoded in your spec file. It should be removed, so as to use rebuilder's own defaults. """ buildarch-instead-of-exclusivearch-tag=""" Use ExclusiveArch instead of BuildArch (or BuildArchitectures) to restrict build on some specific architectures. Only use BuildArch with noarch """ hardcoded-prefix-tag=""" The Prefix tag is hardcoded in your spec file. It should be removed, so as to allow package relocation. """ hardcoded-library-path=""" A library path is hardcoded to one of the following paths: /lib, /usr/lib. It should be replaced by something like /%{_lib} or %{_libdir}. """ configure-without-libdir-spec=""" A configure script is run without specifying the libdir. configure options must be augmented with something like --libdir=%{_libdir} whenever the script supports it. """ "no-%prep-section"=""" The spec file does not contain a %prep section. Even if some packages don't directly need it, section markers may be overridden in rpm's configuration to provide additional 'under the hood' functionality. Add the section, even if empty. """ "no-%build-section"=""" The spec file does not contain a %build section. Even if some packages don't directly need it, section markers may be overridden in rpm's configuration to provide additional 'under the hood' functionality, such as injection of automatic -debuginfo subpackages. Add the section, even if empty. """ "no-%install-section"=""" The spec file does not contain an %install section. Even if some packages don't directly need it, section markers may be overridden in rpm's configuration to provide additional 'under the hood' functionality. Add the section, even if empty. """ "more-than-one-%changelog-section"=""" The spec file unnecessarily contains more than one %changelog section. """ "superfluous-%clean-section"=""" The spec section %clean should not be used any longer. RPM provides its own clean logic. """ "lib-package-without-%mklibname"=""" The package name must be built using %mklibname to allow lib64 and lib32 coexistence. """ "%ifarch-applied-patch"=""" A patch is applied inside an %ifarch block. Patches must be applied on all architectures and may contain necessary configure and/or code patch to be effective only on a given arch. """ prereq-use=""" The use of PreReq is deprecated. In the majority of cases, a plain Requires is enough and the right thing to do. Sometimes Requires(pre), Requires(post), Requires(preun) and/or Requires(postun) can also be used instead of PreReq. """ buildprereq-use=""" The use of BuildPreReq is deprecated, build dependencies are always required before a package can be built. Use plain BuildRequires instead. """ setup-not-in-prep=""" The %setup macro should only be used within the %prep section because it may not expand to anything outside of it and can break the build in unpredictable ways. """ setup-not-quiet=""" Use the -q option to the %setup macro to avoid useless build output from unpacking the sources. """ no-cleaning-of-buildroot=""" You should clean $RPM_BUILD_ROOT in the %clean section and in the beginning of the %install section. Use 'rm -rf $RPM_BUILD_ROOT'. Some rpm configurations do this automatically; if your package is only going to be built in such configurations, you can ignore this warning for the section(s) where your rpm takes care of it. """ rpm-buildroot-usage=""" $RPM_BUILD_ROOT or %{buildroot} must not be touched during %build or %prep stage, as it will break short circuit builds and will not persist to %install stage in a normal build, leading to unexpected package build behavior. """ make-check-outside-check-section=""" Make check or other automated regression test should be run in %check, as they can be disabled with a rpm macro for short circuiting purposes. """ "macro-in-%changelog"=""" Macros are expanded in %changelog too, which can in unfortunate cases lead to the package not building at all, or other subtle unexpected conditions that affect the build. Even when that doesn't happen, the expansion results in possibly 'rewriting history' on subsequent package revisions and generally odd entries eg. in source rpms, which is rarely wanted. Avoid use of macros in %changelog altogether, or use two '%'s to escape them, like '%%foo'. """ depscript-without-disabling-depgen=""" In some common rpm configurations/versions, defining __find_provides and/or __find_requires has no effect if rpm's internal dependency generator has not been disabled for the build. %define _use_internal_dependency_generator to 0 to disable it in the specfile, or don't define __find_provides/requires. """ mixed-use-of-spaces-and-tabs=""" The specfile mixes use of spaces and tabs for indentation, which is a cosmetic annoyance. """ unversioned-explicit-provides=""" The specfile contains an unversioned Provides: token, which will match all older, equal, and newer versions of the provided thing. This may cause update problems and will make versioned dependencies, obsoletions and conflicts on the provided thing useless -- make the Provides versioned if possible. """ unversioned-explicit-obsoletes=""" The specfile contains an unversioned Obsoletes: token, which will match all older, equal and newer versions of the obsoleted thing. This may cause update problems, restrict future package/provides naming, and may match something it was originally not inteded to match -- make the Obsoletes versioned if possible. """ libdir-macro-in-noarch-package=""" The %{_libdir} or %{_lib} macro was found in a noarch package in a section that gets included in binary packages. This is most likely an error because these macros are expanded on the build host and their values vary between architectures, probably resulting in a package that does not work properly on all architectures at runtime. Investigate whether the package is really architecture independent or if some other dir/macro should be instead. """ non-break-space=""" The spec file contains a non-break space, which looks like a regular space in some editors but can lead to obscure errors. It should be replaced by a regular space. """ specfile-error=""" This error occurred when rpmlint used rpm to query the specfile. The error is output by rpm and the message should contain more information. """ comparison-operator-in-deptoken=""" This dependency token contains a comparison operator (<, > or =). This is usually not intended and may be caused by missing whitespace between the token's name, the comparison operator and the version string. """ macro-in-comment=""" There is a unescaped macro after a shell style comment in the specfile. Macros are expanded everywhere, so check if it can cause a problem in this case and escape the macro with another leading % if appropriate. """ patch-fuzz-is-changed=""" The internal patch fuzz value was changed, and could hide patchs issues, or could lead to applying a patch at the wrong location. Usually, this is often the sign that someone didn't check if a patch is still needed and do not want to rediff it. It is usually better to rediff the patch and try to send it upstream. """ rpmlint-2.2.0+ds1/rpmlint/descriptions/SysVInitOnSystemdCheck.toml000066400000000000000000000007171415540642600253230ustar00rootroot00000000000000obsolete-insserv-requirement=""" In systemd based distributions insserv is obsolete. Please remove dependencies on insserv.""" deprecated-init-script=""" SysV init scripts are deprecated. Please migrate to systemd service files.""" deprecated-boot-script=""" SysV boot scripts are deprecated. Please migrate to systemd service files.""" systemd-shadowed-initscript=""" The package contains both an init script and a systemd service file for the same activity.""" rpmlint-2.2.0+ds1/rpmlint/descriptions/TagsCheck.toml000066400000000000000000000157631415540642600226320ustar00rootroot00000000000000invalid-version=""" The version string must not contain the pre, alpha, beta or rc suffixes because when the final version will be out, you will have to use an Epoch tag to make the package upgradable. Instead put it in the release tag, prefixed with something you have control over. """ spelling-error=""" The value of this tag appears to be misspelled. Please double-check. """ no-packager-tag=""" There is no Packager tag in your package. You have to specify a packager using the Packager tag. Ex: Packager: John Doe . """ invalid-packager=""" The packager email must end with an email compatible with the Packager option of rpmlint. Please change it and rebuild your package. """ no-version-tag=""" There is no Version tag in your package. You have to specify a version using the Version tag. """ no-release-tag=""" There is no Release tag in your package. You have to specify a release using the Release tag. """ no-name-tag=""" There is no Name tag in your package. You have to specify a name using the Name tag. """ non-coherent-filename=""" The file which contains the package should be named --..rpm. """ no-major-in-name=""" The major number of the library isn't included in the package's name. """ description-shorter-than-summary=""" The package description should be longer than the summary. """ no-provides=""" Your library package doesn't provide the -devel name without the major version included. """ no-summary-tag=""" There is no Summary tag in your package. You have to describe your package using this tag. """ summary-on-multiple-lines=""" Your summary must fit on one line. """ summary-not-capitalized=""" Summary doesn't begin with a capital letter. """ summary-ended-with-dot=""" Summary ends with a dot. """ summary-has-leading-spaces=""" Summary begins with whitespace which will waste space when displayed. """ no-description-tag=""" The description of the package is empty or missing. """ tag-in-description=""" Something that looks like a tag was found in the package's description. This may indicate a problem where the tag was not actually parsed as a tag but just textual description content, thus being a no-op. Verify if this is the case, and move the tag to a place in the specfile where %description won't fool the specfile parser, and rebuild the package. """ no-group-tag=""" There is no Group tag in your package. You have to specify a valid group in your spec file using the Group tag. """ no-changelogname-tag=""" There is no %changelog tag in your spec file(or it's empty). To fix it, please insert a '%changelog' section in your spec file and add a entry change below it. """ no-version-in-last-changelog=""" The latest changelog entry doesn't contain a version. Please insert the version that is coherent with the version of the package and rebuild it. """ incoherent-version-in-changelog=""" The latest entry in %changelog contains a version identifier that is not coherent with the epoch:version-release tuple of the package. """ changelog-time-overflow=""" The timestamp of the latest entry in %changelog is suspiciously far away in the past. """ changelog-time-in-future=""" The timestamp of the latest entry in %changelog is in the future. """ no-license=""" There is no License tag in your spec file. You have to specify one license for your program (eg. GPL-3.0-only). """ obsolete-not-provided=""" If a package is obsoleted by a compatible replacement, the obsoleted package should also be provided in order to not cause unnecessary dependency breakage. If the obsoleting package is not a compatible replacement for the old one, leave out the Provides. """ invalid-dependency=""" An invalid dependency has been detected. It usually means that the build of the package was buggy. """ no-epoch-tag=""" There is no Epoch tag in your package. """ unreasonable-epoch=""" The value of your Epoch tag is unreasonably large (> 99). """ no-epoch-in-dependency=""" Your package contains a versioned dependency without an Epoch. """ devel-dependency=""" Your package has a dependency on a devel package but it's not a devel package itself. """ invalid-build-requires=""" Your source package contains a dependency not compliant with the lib64 naming. This BuildRequires dependency will not be resolved on lib64 platforms (eg. amd64). """ explicit-lib-dependency=""" You must let rpm find the library dependencies by itself. Do not put unneeded explicit Requires: tags. """ useless-provides=""" This package provides multiple times the same capacity. This means versioned and unversioned symbols are provided at once thus one overshadowing the other. I.e. 'foo' and 'foo = 1.0'. """ requires-on-release=""" This rpm requires a specific release of another package. """ no-url-tag=""" The URL tag is missing. """ name-repeated-in-summary=""" The name of the package is repeated in its summary. Make the summary brief and to the point without including redundant information in it. """ enchant-dictionary-not-found=""" A dictionary for the Enchant spell checking library is not available for the language given in the info message. """ self-obsoletion=""" The package obsoletes itself. This is known to cause errors in various tools and should thus be avoided, usually by using appropriately versioned Obsoletes and/or Provides and avoiding unversioned ones. """ unexpanded-macro=""" This tag contains something that looks like an unexpanded macro; this is often the sign of a misspelling. Please check your specfile. """ private-shared-object-provides=""" A shared object soname provides is provided by a file in a path from which other packages should not directly load shared objects from. Such shared objects should thus not be depended on and they should not result in provides in the containing package. Get rid of the provides if appropriate, for example by filtering it out during build. Note that in some cases this may require disabling rpmbuild's internal dependency generator. """ devel-package-with-non-devel-group=""" The package ends with -devel but does not have a RPM group starting with Development/. """ no-pkg-config-provides=""" The package installs a .pc file but does not provide pkgconfig(..) provides. The most likely reason for that is that it was built without BuildRequires: pkgconfig. Please double check your build dependencies.""" invalid-license-exception=""" The ' with ' license exception of the License tag was not recognized. """ forbidden-controlchar-found=""" This package contains tags which contain forbidden control characters. These are all ASCII characters with a decimal value below 32, except TAB(9), LF(10) and CR(13) """ summary-too-long=""" This package has a summary line greater than 79 characters. Keep the summary below the character limit. """ description-line-too-long=""" This package has a description line of length greater than 79 characters. Break the line into multiple lines to remove the warning. """ non-standard-group=""" This package contains a Group: tag value different from the one defined in ValidGroups inside configdefaults.toml. Make sure both the values match to remove the warning. """ rpmlint-2.2.0+ds1/rpmlint/descriptions/TmpFilesCheck.toml000066400000000000000000000012061415540642600234420ustar00rootroot00000000000000pre-with-tmpfile-creation=""" %pre section contains %tmpfiles_create macro that should be in the %post section instead. """ post-without-tmpfile-creation=""" Please use the %tmpfiles_create macro in %post for each of your tmpfiles.d files if you expect this file or directory to be available after package installation (and before reboot). """ tmpfile-not-regular-file=""" Files in tmpfiles.d need to be regular files. """ tmpfile-not-in-filelist=""" Please add the specified file to your %files section as %ghost so users can easily query who created the file, it gets uninstalled on package removal and finally other rpmlint checks see it. """ rpmlint-2.2.0+ds1/rpmlint/descriptions/ZipCheck.toml000066400000000000000000000011471415540642600224650ustar00rootroot00000000000000bad-crc-in-zip = """ The reported file in the zip fails the CRC check. Usually this is a sign of a corrupt zip file. """ uncompressed-zip = """ The zip file is not compressed. """ class-path-in-manifest = """ The META-INF/MANIFEST.MF file in the jar contains a hardcoded Class-Path. These entries do not work with older Java versions and even if they do work, they are inflexible and usually cause nasty surprises. """ jar-not-indexed = """ The jar file is not indexed, i.e. it does not contain the META-INF/INDEX.LIST file. Indexed jars speed up the class searching process of classloaders in some situations. """ rpmlint-2.2.0+ds1/rpmlint/descriptions/ZyppSyntaxCheck.toml000066400000000000000000000005011415540642600240650ustar00rootroot00000000000000suse-zypp-packageand=""" The 'packageand(package1:package2)' syntax is obsolete, please use boolean dependencies like: 'Supplements: (package1 and package2)' """ suse-zypp-otherproviders=""" The 'otherproviders(symbol)' syntax is obsolete, it is not needed and you can use the 'symbol' directly: 'Conflicts: symbol' """ rpmlint-2.2.0+ds1/rpmlint/filter.py000066400000000000000000000176561415540642600172350ustar00rootroot00000000000000from pathlib import Path import re import textwrap from rpmlint.color import Color from rpmlint.helpers import print_warning import toml class Filter(object): """ Handle all printing/formatting/filtering of the rpmlint output. Nothing gets printed out until the end of all runs and all errors are sorted and formatted based on the rules specified by the user/config """ def __init__(self, config): """ Initialize options from configuration and load rpmlint descriptions. Args: config: Config object with parsed rpmlint configuration. """ # badness stuff self.badness_threshold = config.configuration['BadnessThreshold'] self.badness = config.configuration['Scoring'] self.strict = config.strict # list of filter regexes self.filters_regexes = [re.compile(f) for f in config.configuration['Filters']] # list of blocked filters self.blocked_filters = set(config.configuration['BlockedFilters']) # set of filters that are actually used in add_info self.used_filters = set() self.rpmlintrc_filters = config.rpmlintrc_filters # informative or quiet self.info = config.info # How many bad hits we already collected while collecting issues self.score = 0 # Dictionary containing mapped values of descriptions for the errors. self.error_details = {} # Load it up with the toml descriptions self.error_details.update(self._load_descriptions()) # Counter of how many issues we encountered self.printed_messages = {'I': 0, 'W': 0, 'E': 0} # Messages self.results = [] @staticmethod def _load_descriptions(): """ Load rpmlint error/warning description texts from toml files. Detailed description for every rpmlint error/warning is stored in descriptions/.toml file. Returns: A dictionary mapping error/warning/info names to their descriptions. """ descriptions = {} descr_folder = Path(__file__).parent / 'descriptions' try: description_files = sorted(descr_folder.glob('*.toml')) descriptions = toml.load(description_files) except toml.decoder.TomlDecodeError as terr: print_warning(f'(none): W: unable to parse description files: {terr}') return descriptions def add_info(self, level, package, rpmlint_issue, *details): """ Format rpmlint issue output and add it to self.results. It creates formatted and colored output consisting of all information about rpmlint issue given by the arguments. Args: level: A string with level of the rpmlint issue ('E' - Error, 'W' - Warning, 'I' - Info package: Pkg object representing processed package rpmlint_issue: A string representing the name of the rpmlint issue *details: Details of the rpmlint issue """ # filename in some cases can contain tmp paths and we don't need it # for the printout filename = Path(package.name).name # we can get badness treshold badness = None if rpmlint_issue in self.badness: badness = int(self.badness[rpmlint_issue]) # If we have any badness configured then we 'stricten' and call the # result Error. Otherwise we downgrade the error to Warn. if badness > 0: level = 'E' elif level == 'E': level = 'W' # allow strict reporting where we override levels and treat everything # as an error if self.strict: level = 'E' if badness is None: badness = 1 if level == 'E' else 0 # set coloring if level == 'E': lvl_color = Color.Red elif level == 'W': lvl_color = Color.Yellow else: lvl_color = Color.Bold # compile the message line = f'{package.current_linenum}:' if package.current_linenum else '' arch = f'.{package.arch}' if package.arch else '' bad_output = f' (Badness: {badness})' if badness > 1 else '' detail_output = '' for detail in details: if detail: detail_output += f' {detail}' result = f'{Color.Bold}{filename}{arch}:{line}{Color.Reset} {lvl_color}{level}: {rpmlint_issue}{Color.Reset}{bad_output}{detail_output}' # filter by the result message result_no_color = f'{filename}{arch}:{line} {level}: {rpmlint_issue}{detail_output}' # unused-rpmlintrc-filter warnings should be skipped if rpmlint_issue != 'unused-rpmlintrc-filter' and rpmlint_issue not in self.blocked_filters: for f in self.filters_regexes: if f.search(result_no_color): self.used_filters.add(f.pattern) return # raise the counters self.score += badness self.printed_messages[level] += 1 self.results.append(result) def print_results(self, results, config=None): """ Provide all the information about the specified package. If there is description to be provided it needs to be provided only once per rpmlint_issue. Args: results: A list with rpmlint messages. config: parsed configuration file that is used as a source for new description strings Returns: A string with final rpmlint output. """ output = '' results.sort(key=self.__diag_sortkey, reverse=True) last_issue = '' for diag in results: if self.info: rpmlint_issue = diag.split()[2].rstrip(Color.Reset) # print out details for each rpmlint_issue we had if rpmlint_issue != last_issue: if last_issue: output += self.get_description(last_issue, config) last_issue = rpmlint_issue output += diag + '\n' if self.info and last_issue: output += self.get_description(last_issue, config) # normalize the output as rpm 4.15 uses surrogates output = output.encode('utf-8', errors='surrogateescape').decode('utf-8', errors='replace') return output def get_description(self, rpmlint_issue, config=None): """ Get description for specified rpmlint issue (error, warning or info). Args: rpmlint_issue: A string with the rpmlint error/warning/info name config: parsed configuration file that is used as a source for custom description strings ([Descriptions] table in toml syntax) Returns: A string with description for specified rpmlint issue. Empty content does not cause an issue and we just return empty content """ description = '' if rpmlint_issue in self.error_details: # Update rpmlint error descriptions from configuration file if config and config.configuration.get('Descriptions').get(rpmlint_issue): self.error_details[rpmlint_issue] = config.configuration['Descriptions'][rpmlint_issue] # we need 2 enters at the end for whitespace purposes description = textwrap.fill(self.error_details[rpmlint_issue], 78, break_on_hyphens=False) + '\n\n' return description def __diag_sortkey(self, x): """ Sorting helper, xs[1] is packagename line architecture xs[2] is the reason of the error """ xs = x.split() return (xs[2], xs[1]) def validate_filters(self, pkg): for f in self.rpmlintrc_filters: if f not in self.used_filters: self.add_info('E', pkg, 'unused-rpmlintrc-filter', f'"{f}"') rpmlint-2.2.0+ds1/rpmlint/helpers.py000066400000000000000000000023721415540642600173770ustar00rootroot00000000000000# File containing various helper functions used across rpmlint import os from shutil import get_terminal_size import sys from rpmlint.color import Color ENGLISH_ENVIROMENT = dict(os.environ, LC_ALL='en_US.UTF-8') def string_center(message, filler=' '): """ Create string centered of the terminal """ cols, rows = get_terminal_size() return (f' {message} ').center(cols, filler) def print_centered(message, filler=' '): """ Print message in the center of a terminal """ print(string_center(message, filler)) def print_warning(message): """ Print warning message to stderr. """ print(f'{Color.Red}{message}{Color.Reset}', file=sys.stderr) def byte_to_string(item): """ Convert byte items to strings """ # empty stuff or already existing string stays if item is None or isinstance(item, str): return item # if we have a list/tuple we have to recurse if isinstance(item, (list, tuple)): return [byte_to_string(i) for i in item] # everything else shall be decoded and fails replaced return item.decode(encoding='UTF-8', errors='replace') def readlines(path): with open(path, 'rb') as fobj: for line in fobj: yield byte_to_string(line) rpmlint-2.2.0+ds1/rpmlint/lddparser.py000066400000000000000000000075251415540642600177220ustar00rootroot00000000000000import re import subprocess from rpmlint.helpers import ENGLISH_ENVIROMENT class LddParser: """ Class contains all information obtained by ldd command about undefined symbols and unused direct dependencies. Parse these 2 outputs: $ ldd -u libnss-unused-dependency.so Unused direct dependencies: /lib/libnss_files.so.2 $ ldd -r libthread-undefined-symbol.so linux-gate.so.1 (0xf7fce000) libc.so.6 => /lib/libc.so.6 (0xf7d9a000) /lib/ld-linux.so.2 (0xf7fcf000) undefined symbol: ps_pdwrite (./libthread-undefined-symbol.so) undefined symbol: ps_pglobal_lookup (./libthread-undefined-symbol.so) undefined symbol: ps_lsetregs (./libthread-undefined-symbol.so) undefined symbol: ps_getpid (./libthread-undefined-symbol.so) undefined symbol: ps_lgetfpregs (./libthread-undefined-symbol.so) undefined symbol: ps_lsetfpregs (./libthread-undefined-symbol.so) undefined symbol: ps_lgetregs (./libthread-undefined-symbol.so) undefined symbol: ps_pdread (./libthread-undefined-symbol.so) undefined symbol: gss_release_cred, version gssapi_krb5_2_MIT (./test/ldd/libtirpc.so.3.0.0) undefined symbol: gss_canonicalize_name, version gssapi_krb5_2_MIT (./test/ldd/libtirpc.so.3.0.0) undefined symbol: gss_pname_to_uid, version gssapi_krb5_2_MIT (./test/ldd/libtirpc.so.3.0.0) undefined symbol: gss_accept_sec_context, version gssapi_krb5_2_MIT (./test/ldd/libtirpc.so.3.0.0) undefined symbol: gss_verify_mic, version gssapi_krb5_2_MIT (./test/ldd/libtirpc.so.3.0.0) undefined symbol: gss_get_mic, version gssapi_krb5_2_MIT (./test/ldd/libtirpc.so.3.0.0) """ unused_regex = re.compile(r'^\s+(?P\S+)') undef_regex = re.compile(r'^undefined symbol:\s+(?P[^, ]+)') def __init__(self, pkgfile_path, path, is_installed_pkg): self.pkgfile_path = pkgfile_path self.dependencies = [] self.unused_dependencies = [] self.undefined_symbols = [] self.parsing_failed_reason = None if is_installed_pkg: self.parse_dependencies() self.parse_undefined_symbols() def parse_dependencies(self): r = subprocess.run(['ldd', '-u', self.pkgfile_path], encoding='utf8', stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=ENGLISH_ENVIROMENT) if r.returncode == 0: return lines = r.stdout.splitlines() is_unused = False for line in lines: if line.startswith('Unused direct dependencies:'): is_unused = True elif is_unused: unused = self.unused_regex.search(line) if unused: self.unused_dependencies.append(unused.group('lib')) else: is_unused = False def parse_undefined_symbols(self): r = subprocess.run(['ldd', '-r', self.pkgfile_path], encoding='utf8', stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=ENGLISH_ENVIROMENT) # here ldd should always return 0 if r.returncode != 0: self.parsing_failed_reason = r.stderr return lines = r.stdout.splitlines() for line in lines: r = self.undef_regex.search(line) if r: self.undefined_symbols.append(r.group('symbol')) else: self.dependencies.append(line.strip()) # run c++filt demangler for all collected symbols if self.undefined_symbols: r = subprocess.run(['c++filt'] + self.undefined_symbols, encoding='utf8', stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=ENGLISH_ENVIROMENT) if r.returncode != 0: self.parsing_failed_reason = r.stderr else: self.undefined_symbols = r.stdout.splitlines() rpmlint-2.2.0+ds1/rpmlint/lint.py000066400000000000000000000304261415540642600167040ustar00rootroot00000000000000import cProfile import importlib import operator from pstats import Stats import sys from tempfile import gettempdir import time from rpmlint.color import Color from rpmlint.config import Config from rpmlint.filter import Filter from rpmlint.helpers import print_warning, string_center from rpmlint.pkg import FakePkg, getInstalledPkgs, Pkg from rpmlint.version import __version__ class Lint(object): """ Generic object handling the basic rpmlint operations """ def __init__(self, options): # initialize configuration self.checks = {} self.options = options self.packages_checked = 0 self.specfiles_checked = 0 self.check_duration = {} if options['config']: self.config = Config(options['config']) else: self.config = Config() if options['profile']: self.profile = cProfile.Profile() self.profile.enable() else: self.profile = None if options['rpmlintrc']: options['rpmlintrc'] = [options['rpmlintrc']] self._load_rpmlintrc() if options['verbose']: self.config.info = options['verbose'] if options['strict']: self.config.strict = options['strict'] if options['permissive']: self.config.permissive = options['permissive'] if not self.config.configuration['ExtractDir']: self.config.configuration['ExtractDir'] = gettempdir() # initialize output buffer self.output = Filter(self.config) # preload the check list if we not print config # some of the config values are transformed e.g. to regular # expressions if not self.options['print_config']: self.load_checks() def _run(self): start = time.monotonic() retcode = 0 # if we just want to print config, do so and leave if self.options['print_config']: self.print_config() return retcode # just explain the error and abort too if self.options['explain']: self.print_explanation(self.options['explain'], self.config) return retcode # if there are installed arguments just load them up as extra # items to the rpmfile option if self.options['installed']: self.validate_installed_packages(self._load_installed_rpms(self.options['installed'])) # if no exclusive option is passed then just loop over all the # arguments that are supposed to be either rpm or spec files self.validate_files(self.options['rpmfile']) self._print_header() print(self.output.print_results(self.output.results, self.config), end='') quit_color = Color.Bold if self.output.printed_messages['W'] > 0: quit_color = Color.Yellow if self.output.badness_threshold > 0 and self.output.score > self.output.badness_threshold: msg = string_center(f'Badness {self.output.score} exceeds threshold {self.output.badness_threshold}, aborting.', '-') print(f'{Color.Red}{msg}{Color.Reset}') quit_color = Color.Red retcode = 66 elif self.output.printed_messages['E'] > 0 and not self.config.permissive: quit_color = Color.Red retcode = 64 self._maybe_print_reports() duration = time.monotonic() - start msg = string_center(f'{self.packages_checked} packages and {self.specfiles_checked} specfiles checked; ' f'{self.output.printed_messages["E"]} errors, {self.output.printed_messages["W"]} warnings, ' f'{self.output.score} badness; has taken {duration:.1f} s', '=') print(f'{quit_color}{msg}{Color.Reset}') return retcode def run(self): try: return self._run() except KeyboardInterrupt as e: self._maybe_print_reports() raise e def _maybe_print_reports(self): if self.options['time_report']: self._print_time_report() if self.profile: self._print_cprofile() def _get_color_time_report_value(self, fraction): if fraction > 25: color = Color.Red elif fraction > 5: color = Color.Yellow else: color = '' return f'{color}{fraction:17.1f}{Color.Reset}' def _print_time_report(self): THRESHOLD = 1 total = sum(self.check_duration.values()) checked_files = [check.checked_files for check in self.checks.values() if check.checked_files] total_checked_files = max(checked_files) if checked_files else '' print(f'\n{Color.Bold}Check time report{Color.Reset} (>{THRESHOLD}%):') print(f'{Color.Bold} {"Check":32s} {"Duration (in s)":>12} {"Fraction (in %)":>17} Checked files{Color.Reset}') for check, duration in sorted(self.check_duration.items(), key=operator.itemgetter(1), reverse=True): fraction = 100.0 * duration / total if fraction < THRESHOLD: continue checked_files = self.checks[check].checked_files if not checked_files: checked_files = '' print(f' {check:32s} {duration:15.2f} {self._get_color_time_report_value(fraction)} {checked_files:>14}') print(f' {"TOTAL":32s} {total:15.2f} {100:17.2f} {total_checked_files:>14}') def _print_cprofile(self): N = 30 print(f'\n{Color.Bold}cProfile report:{Color.Reset}') self.profile.disable() stats = Stats(self.profile) stats.sort_stats('cumulative').print_stats(N) print('========================================================') stats.sort_stats('ncalls').print_stats(N) print('========================================================') stats.sort_stats('tottime').print_stats(N) def _load_installed_rpms(self, packages): existing_packages = [] for name in packages: pkg = getInstalledPkgs(name) if pkg: existing_packages.extend(pkg) else: print_warning(f'(none): E: there is no installed rpm "{name}".') return existing_packages def _load_rpmlintrc(self): """ Load rpmlintrc from argument or load up from folder """ if self.options['rpmlintrc']: # Right now, we allow loading of just a single file, but the 'opensuse' # branch contains auto-loading mechanism that can eventually load # multiple files. for rcfile in self.options['rpmlintrc']: self.config.load_rpmlintrc(rcfile) else: # load only from the same folder specname.rpmlintrc or specname-rpmlintrc # do this only in a case where there is one folder parameter or one file # to avoid multiple folders handling rpmlintrc = [] if not len(self.options['rpmfile']) == 1: return pkg = self.options['rpmfile'][0] if pkg.is_file(): pkg = pkg.parent rpmlintrc += sorted(pkg.glob('*.rpmlintrc')) rpmlintrc += sorted(pkg.glob('*-rpmlintrc')) if len(rpmlintrc) > 1: # multiple rpmlintrcs are highly undesirable print_warning('There are multiple items to be loaded for rpmlintrc, ignoring them: {}.'.format(' '.join(map(str, rpmlintrc)))) elif len(rpmlintrc) == 1: self.options['rpmlintrc'] = rpmlintrc[0] self.config.load_rpmlintrc(rpmlintrc[0]) def _print_header(self): """ Print out header information about the state of the rpmlint prior printing out the check report. """ intro = string_center('rpmlint session starts', '=') print(f'{Color.Bold}{intro}{Color.Reset}') print(f'rpmlint: {__version__}') print('configuration:') for config in self.config.conf_files: print(f' {config}') if self.options['rpmlintrc']: rpmlintrc = self.options['rpmlintrc'] print(f'rpmlintrc: {rpmlintrc}') no_checks = len(self.config.configuration['Checks']) no_pkgs = len(self.options['installed']) + len(self.options['rpmfile']) print(f'{Color.Bold}checks: {no_checks}, packages: {no_pkgs}{Color.Reset}') print('') def validate_installed_packages(self, packages): for pkg in packages: self.run_checks(pkg, pkg == packages[-1]) def validate_files(self, files): """ Run all the check for passed file list """ if not files: if self.packages_checked == 0: # print warning only if we didn't process even installed files print_warning('There are no files to process nor additional arguments.') print_warning('Nothing to do, aborting.') return # check all elements if they are a folder or a file with proper suffix # and expand everything packages = self._expand_filelist(files) # Sort the files so that the output is stable packages = sorted(packages) for pkg in packages: self.validate_file(pkg, pkg == packages[-1]) # run post check function for checker in self.checks.values(): checker.after_checks() def _expand_filelist(self, files): packages = [] for pkg in files: if pkg.is_file() and self._check_valid_suffix(pkg): packages.append(pkg) elif pkg.is_dir(): packages.extend(self._expand_filelist(pkg.iterdir())) return packages @staticmethod def _check_valid_suffix(filename): if any(ext == filename.suffix for ext in ['.rpm', '.spm', '.spec']): return True return False def validate_file(self, pname, is_last): try: if pname.suffix == '.rpm' or pname.suffix == '.spm': with Pkg(pname, self.config.configuration['ExtractDir'], verbose=self.config.info) as pkg: self.run_checks(pkg, is_last) elif pname.suffix == '.spec': with FakePkg(pname) as pkg: self.run_checks(pkg, is_last) except Exception as e: print_warning(f'(none): E: fatal error while reading {pname}: {e}') if self.config.info: raise e else: sys.exit(3) def run_checks(self, pkg, is_last): spec_checks = isinstance(pkg, FakePkg) for checker in self.checks: if checker not in self.check_duration: self.check_duration[checker] = 0 start = time.monotonic() fn = self.checks[checker].check_spec if spec_checks else self.checks[checker].check fn(pkg) self.check_duration[checker] += time.monotonic() - start # validate used filters in rpmlintrc if is_last: self.output.validate_filters(pkg) if spec_checks: self.specfiles_checked += 1 else: self.packages_checked += 1 def print_config(self): """ Just output the current configuration """ self.config.print_config() def print_explanation(self, messages, config): """ Print out detailed explanation for the specified messages """ for message in messages: explanation = self.output.get_description(message, config) if not explanation: explanation = 'Unknown message, please report a bug if the description should be present.\n\n' print(f'{message}:\n{explanation}') def load_checks(self): """ Load all checks based on the config, skipping those already loaded SingletonTM """ for check in self.config.configuration['Checks']: if check in self.checks: continue self.checks[check] = self.load_check(check) def load_check(self, name): """Load a (check) module by its name, unless it is already loaded.""" module = importlib.import_module(f'.{name}', package='rpmlint.checks') klass = getattr(module, name) obj = klass(self.config, self.output) return obj rpmlint-2.2.0+ds1/rpmlint/objdumpparser.py000066400000000000000000000041611415540642600206100ustar00rootroot00000000000000import subprocess from rpmlint.helpers import ENGLISH_ENVIROMENT class ObjdumpParser: """ Class contains all information obtained by objdump command. Right now, we are interested in DW_TAG_compile_unit of debug info. Example output of objdump: <0>: Abbrev Number: 1 (DW_TAG_compile_unit) DW_AT_stmt_list : 0x0 <10> DW_AT_low_pc : 0x927840 <18> DW_AT_high_pc : 0x92786b <20> DW_AT_name : (indirect string, offset: 0x0): ../sysdeps/x86_64/start.S <24> DW_AT_comp_dir : (indirect string, offset: 0x1a): /home/abuild/rpmbuild/BUILD/glibc-2.31/csu <28> DW_AT_producer : (indirect string, offset: 0x45): GNU AS 2.33.1 <2c> DW_AT_language : 32769 (MIPS assembler) Compilation Unit @ offset 0x2e: Length: 0x3c (32-bit) """ dw_at_prefix = 'DW_AT_' def __init__(self, pkgfile_path, path): self.pkgfile_path = pkgfile_path self.compile_units = [] self.parsing_failed_reason = None self.parse_dwarf_compilation_units() def parse_dwarf_compilation_units(self): r = subprocess.run(['objdump', '--dwarf=info', '--dwarf-depth=1', self.pkgfile_path], encoding='utf8', stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=ENGLISH_ENVIROMENT) # here ldd should always return 0 if r.returncode != 0: self.parsing_failed_reason = r.stderr return lines = r.stdout.splitlines() for i, line in enumerate(lines): if 'DW_TAG_compile_unit' in line: # We parse all DW_at_ flags that follow the DW_TAG_compile_unit i += 1 cu_data = {} while self.dw_at_prefix in lines[i]: current_line = lines[i] current_line = current_line[current_line.find(self.dw_at_prefix) + len(self.dw_at_prefix):] parts = [t.strip() for t in current_line.split(':')] cu_data[parts[0]] = parts[-1] i += 1 self.compile_units.append(cu_data) rpmlint-2.2.0+ds1/rpmlint/pkg.py000066400000000000000000000700661415540642600165230ustar00rootroot00000000000000import bz2 from collections import namedtuple import gzip import lzma import os from pathlib import Path import re from shlex import quote import stat import subprocess import tempfile from urllib.parse import urljoin try: import magic # TODO: magic.MAGIC_COMPRESS when PkgFile gets decompress support. _magic = magic.open(magic.MAGIC_NONE) _ = _magic.descriptor # magic >= 5.05 needed _magic.load() except ImportError: _magic = None import rpm from rpmlint.helpers import byte_to_string, ENGLISH_ENVIROMENT, print_warning from rpmlint.pkgfile import PkgFile import zstd DepInfo = namedtuple('DepInfo', ('name', 'flags', 'version')) # 64: RPMSENSE_PREREQ is 0 with rpm 4.4..4.7, we want 64 here in order # to do the right thing with those versions and packages built with other # rpm versions PREREQ_FLAG = (rpm.RPMSENSE_PREREQ or 64) | rpm.RPMSENSE_SCRIPT_PRE | \ rpm.RPMSENSE_SCRIPT_POST | rpm.RPMSENSE_SCRIPT_PREUN | \ rpm.RPMSENSE_SCRIPT_POSTUN SCRIPT_TAGS = [ (rpm.RPMTAG_PREIN, rpm.RPMTAG_PREINPROG, '%pre'), (rpm.RPMTAG_POSTIN, rpm.RPMTAG_POSTINPROG, '%post'), (rpm.RPMTAG_PREUN, rpm.RPMTAG_PREUNPROG, '%preun'), (rpm.RPMTAG_POSTUN, rpm.RPMTAG_POSTUNPROG, '%postun'), (rpm.RPMTAG_TRIGGERSCRIPTS, rpm.RPMTAG_TRIGGERSCRIPTPROG, '%trigger'), (rpm.RPMTAG_PRETRANS, rpm.RPMTAG_PRETRANSPROG, '%pretrans'), (rpm.RPMTAG_POSTTRANS, rpm.RPMTAG_POSTTRANSPROG, '%posttrans'), (rpm.RPMTAG_VERIFYSCRIPT, rpm.RPMTAG_VERIFYSCRIPTPROG, '%verifyscript'), # file triggers: rpm >= 4.12.90 (getattr(rpm, 'RPMTAG_FILETRIGGERSCRIPTS', 5066), getattr(rpm, 'RPMTAG_FILETRIGGERSCRIPTPROG', 5067), '%filetrigger'), (getattr(rpm, 'RPMTAG_TRANSFILETRIGGERSCRIPTS', 5076), getattr(rpm, 'RPMTAG_TRANSFILETRIGGERSCRIPTPROG', 5077), '%transfiletrigger'), ] RPM_SCRIPTLETS = ('pre', 'post', 'preun', 'postun', 'pretrans', 'posttrans', 'trigger', 'triggerin', 'triggerprein', 'triggerun', 'triggerpostun', 'verifyscript', 'filetriggerin', 'filetrigger', 'filetriggerun', 'filetriggerpostun', 'transfiletriggerin', 'transfiletrigger', 'transfiletriggerun', 'transfiletriggerun', 'transfiletriggerpostun') gzip_regex = re.compile(r'\.t?gz?$') bz2_regex = re.compile(r'\.t?bz2?$') xz_regex = re.compile(r'\.(t[xl]z|xz|lzma)$') zst_regex = re.compile(r'\.zst$') def catcmd(fname): """Get a 'cat' command that handles possibly compressed files.""" fname = str(fname) cat = 'gzip -dcf' if bz2_regex.search(fname): cat = 'bzip2 -dcf' elif xz_regex.search(fname): cat = 'xz -dc' elif zst_regex.search(fname): cat = 'zstd -dc' return cat def compression_algorithm(fname): """Return compression algorithm based on filename if known, None otherwise.""" fname = str(fname) if gzip_regex.search(fname): return gzip elif bz2_regex.search(fname): return bz2 elif xz_regex.search(fname): return lzma elif zst_regex.search(fname): return zstd else: return None def is_utf8(fname): compression = compression_algorithm(fname) if compression is None: with open(fname, 'rb') as f: return is_utf8_bytestr(f.read()) with compression.open(fname, 'rb') as f: try: return is_utf8_bytestr(f.read()) except OSError: return True def is_utf8_bytestr(s): """Returns True whether the given text is UTF-8. Due to changes in rpm, needs to handle both bytes and unicode.""" try: if hasattr(s, 'decode'): s.decode('utf-8') elif hasattr(s, 'encode'): s.encode('utf-8') else: unexpected = type(s).__name__ raise TypeError( 'Expected str/unicode/bytes, not {}'.format(unexpected)) except UnicodeError: return False return True def has_forbidden_controlchars(val): if isinstance(val, str) or isinstance(val, bytes): string = val if isinstance(val, bytes): val = memoryview(val) for c in val: if isinstance(c, str): c = ord(c) if c < 32 and (c not in (9, 10, 13)): return string if isinstance(val, tuple) or isinstance(val, list): for item in val: return has_forbidden_controlchars(item) return False # from yum 3.2.27, rpmUtils.miscutils, with rpmlint modifications def compareEVR(evr1, evr2): (e1, v1, r1) = evr1 (e2, v2, r2) = evr2 # return 1: a is newer than b # 0: a and b are the same version # -1: b is newer than a # rpmlint mod: don't stringify None epochs to 'None' strings if e1 is not None: e1 = str(e1) v1 = str(v1) r1 = str(r1) if e2 is not None: e2 = str(e2) v2 = str(v2) r2 = str(r2) rc = rpm.labelCompare((e1, v1, r1), (e2, v2, r2)) return rc # from yum 3.2.27, rpmUtils.miscutils, with rpmlint modifications def rangeCompare(reqtuple, provtuple): """returns true if provtuple satisfies reqtuple""" (reqn, reqf, (reqe, reqv, reqr)) = reqtuple (n, f, (e, v, r)) = provtuple if reqn != n: return 0 # unversioned satisfies everything if not f or not reqf: return 1 # and you thought we were done having fun # if the requested release is left out then we have # to remove release from the package prco to make sure the match # is a success - ie: if the request is EQ foo 1:3.0.0 and we have # foo 1:3.0.0-15 then we have to drop the 15 so we can match if reqr is None: r = None # rpmlint mod: don't mess with provided Epoch, doing so breaks e.g. # 'Requires: foo < 1.0' should not be satisfied by 'Provides: foo = 1:0.5' # if reqe is None: # e = None if reqv is None: # just for the record if ver is None then we're going to segfault v = None # if we just require foo-version, then foo-version-* will match if r is None: reqr = None rc = compareEVR((e, v, r), (reqe, reqv, reqr)) # does not match unless if rc >= 1: if reqf in ['GT', 'GE', 4, 12]: return 1 if reqf in ['EQ', 8]: if f in ['LE', 10, 'LT', 2]: return 1 if reqf in ['LE', 'LT', 'EQ', 10, 2, 8]: if f in ['LE', 'LT', 10, 2]: return 1 if rc == 0: if reqf in ['GT', 4]: if f in ['GT', 'GE', 4, 12]: return 1 if reqf in ['GE', 12]: if f in ['GT', 'GE', 'EQ', 'LE', 4, 12, 8, 10]: return 1 if reqf in ['EQ', 8]: if f in ['EQ', 'GE', 'LE', 8, 12, 10]: return 1 if reqf in ['LE', 10]: if f in ['EQ', 'LE', 'LT', 'GE', 8, 10, 2, 12]: return 1 if reqf in ['LT', 2]: if f in ['LE', 'LT', 10, 2]: return 1 if rc <= -1: if reqf in ['GT', 'GE', 'EQ', 4, 12, 8]: if f in ['GT', 'GE', 4, 12]: return 1 if reqf in ['LE', 'LT', 10, 2]: return 1 # if rc >= 1: # if reqf in ['GT', 'GE', 4, 12]: # return 1 # if rc == 0: # if reqf in ['GE', 'LE', 'EQ', 8, 10, 12]: # return 1 # if rc <= -1: # if reqf in ['LT', 'LE', 2, 10]: # return 1 return 0 # from yum 3.2.23, rpmUtils.miscutils, with rpmlint modifications def formatRequire(name, flags, evr): s = name if flags: if flags & (rpm.RPMSENSE_LESS | rpm.RPMSENSE_GREATER | rpm.RPMSENSE_EQUAL): s = s + ' ' if flags & rpm.RPMSENSE_LESS: s = s + '<' if flags & rpm.RPMSENSE_GREATER: s = s + '>' if flags & rpm.RPMSENSE_EQUAL: s = s + '=' s = '%s %s' % (s, versionToString(evr)) return s def versionToString(evr): if not isinstance(evr, (list, tuple)): # assume string return evr ret = '' if evr[0] is not None and evr[0] != '': ret += str(evr[0]) + ':' if evr[1] is not None: ret += evr[1] if evr[2] is not None and evr[2] != '': ret += '-' + evr[2] return ret # from yum 3.2.23, rpmUtils.miscutils, with some rpmlint modifications def stringToVersion(verstring): if verstring in (None, ''): return (None, None, None) epoch = None i = verstring.find(':') if i != -1: try: epoch = str(int(verstring[:i])) except ValueError: # garbage in epoch, ignore it pass i += 1 j = verstring.find('-', i) if j != -1: if verstring[i:j] == '': version = None else: version = verstring[i:j] release = verstring[j + 1:] else: if verstring[i:] == '': version = None else: version = verstring[i:] release = None return (epoch, version, release) def parse_deps(line): """ Parse provides/requires/conflicts/obsoletes line to list of (name, flags, (epoch, version, release)) tuples. """ prcos = [] tokens = re.split(r'[\s,]+', line.strip()) # Drop line continuation backslash in multiline macro definition (for # spec file parsing), e.g. # [...] \ # Obsoletes: foo-%1 <= 1.0.0 \ # [...] \ # (yes, this is an ugly hack and we probably have other problems with # multiline macro definitions elsewhere...) if tokens[-1] == '\\': del tokens[-1] prco = [] while tokens: token = tokens.pop(0) if not token: # skip empty tokens continue plen = len(prco) if plen == 0: prco.append(token) elif plen == 1: flags = 0 if token[0] in ('=', '<', '<=', '>', '>='): # versioned, flags if '=' in token: flags |= rpm.RPMSENSE_EQUAL if '<' in token: flags |= rpm.RPMSENSE_LESS if '>' in token: flags |= rpm.RPMSENSE_GREATER prco.append(flags) else: # no flags following name, treat as unversioned, add and reset prco.extend((flags, (None, None, None))) prcos.append(tuple(prco)) prco = [token] elif plen == 2: # last token of versioned one, add and reset prco.append(stringToVersion(token)) prcos.append(tuple(prco)) prco = [] plen = len(prco) if plen: if plen == 1: prco.extend((0, (None, None, None))) elif plen == 2: prco.append((None, None, None)) prcos.append(tuple(prco)) return prcos def get_magic(path): # file() method evaluates every file twice with python2, # use descriptor() method instead try: fd = os.open(path, os.O_RDONLY) magic = byte_to_string(_magic.descriptor(fd)) os.close(fd) return magic except OSError: return '' # classes representing package class AbstractPkg(object): def cleanup(self): pass def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self.cleanup() class Pkg(AbstractPkg): _magic_from_compressed_re = re.compile(r'\([^)]+\s+compressed\s+data\b') def __init__(self, filename, dirname, header=None, is_source=False, extracted=False, verbose=False): self.filename = filename self.extracted = extracted self.dirname = self.dir_name(dirname, verbose) self.current_linenum = None self._req_names = -1 if header: self.header = header self.is_source = is_source else: # Create a package object from the file name ts = rpm.TransactionSet() # Don't check signatures here... ts.setVSFlags(rpm._RPMVSF_NOSIGNATURES) fd = os.open(filename, os.O_RDONLY) try: self.header = ts.hdrFromFdno(fd) finally: os.close(fd) self.is_source = not self.header[rpm.RPMTAG_SOURCERPM] self.name = self[rpm.RPMTAG_NAME] (self.requires, self.prereq, self.provides, self.conflicts, self.obsoletes, self.recommends, self.suggests, self.enhances, self.supplements) = self._gatherDepInfo() self.req_names = [x[0] for x in self.requires + self.prereq] self.files = self._gatherFilesInfo() self.config_files = [x.name for x in self.files.values() if x.is_config] self.doc_files = [x.name for x in self.files.values() if x.is_doc] self.ghost_files = [x.name for x in self.files.values() if x.is_ghost] self.noreplace_files = [x.name for x in self.files.values() if x.is_noreplace] self.missingok_files = [x.name for x in self.files.values() if x.is_missingok] if self.is_no_source: self.arch = 'nosrc' elif self.is_source: self.arch = 'src' else: self.arch = self.header.format('%{ARCH}') # Return true if the package is a nosource package. # NoSource files are ghosts in source packages. @property def is_no_source(self): return self.is_source and self.ghost_files # access the tags like an array def __getitem__(self, key): try: val = self.header[key] except KeyError: val = [] if val == []: return None else: # Note that text tags we want to try decoding for real in TagsCheck # such as summary, description and changelog are not here. if key in (rpm.RPMTAG_NAME, rpm.RPMTAG_VERSION, rpm.RPMTAG_RELEASE, rpm.RPMTAG_ARCH, rpm.RPMTAG_GROUP, rpm.RPMTAG_BUILDHOST, rpm.RPMTAG_LICENSE, rpm.RPMTAG_HEADERI18NTABLE, rpm.RPMTAG_PACKAGER, rpm.RPMTAG_SOURCERPM, rpm.RPMTAG_DISTRIBUTION, rpm.RPMTAG_VENDOR) \ or key in (x[0] for x in SCRIPT_TAGS) \ or key in (x[1] for x in SCRIPT_TAGS): val = byte_to_string(val) if key == rpm.RPMTAG_GROUP and val == 'Unspecified': val = None return val # return the name of the directory where the package is extracted def dirName(self): return self.dirname def dir_name(self, dirname, verbose): return self._extract(dirname, verbose) # extract rpm contents def _extract(self, dirname, verbose): if not Path(dirname).is_dir(): print_warning('Unable to access dir %s' % dirname) elif dirname == '/': # it is an InstalledPkg pass else: self.__tmpdir = tempfile.TemporaryDirectory( prefix='rpmlint.%s.' % Path(self.filename).name, dir=dirname ) dirname = self.__tmpdir.name # TODO: sequence based command invocation # TODO: warn some way if this fails (e.g. rpm2cpio not installed) # BusyBox' cpio does not support '-D' argument and the only safe # usage is doing chdir before invocation. filename = Path(self.filename).resolve() cwd = os.getcwd() os.chdir(dirname) command_str = f'rpm2cpio {quote(str(filename))} | cpio -id ; chmod -R +rX .' stderr = None if verbose else subprocess.DEVNULL subprocess.check_output(command_str, shell=True, env=ENGLISH_ENVIROMENT, stderr=stderr) os.chdir(cwd) self.extracted = True return dirname def checkSignature(self): ret = subprocess.run(('rpm', '-Kv', self.filename), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=ENGLISH_ENVIROMENT) text = ret.stdout.decode() if text.endswith('\n'): text = text[:-1] return ret.returncode, text # remove the extracted files from the package def cleanup(self): if self.extracted and self.dirname: self.__tmpdir.cleanup() def grep(self, regex, filename): """Grep regex from a file, return matching line numbers.""" ret = [] lineno = 0 try: with open(Path(self.dirName() or '/', filename.lstrip('/'))) as in_file: for line in in_file: lineno += 1 if regex.search(line): ret.append(str(lineno)) break except Exception: pass return ret def langtag(self, tag, lang): """Get value of tag in the given language.""" # LANGUAGE trumps other env vars per GNU gettext docs, see also #166 orig = os.environ.get('LANGUAGE') os.environ['LANGUAGE'] = lang ret = self[tag] if orig is not None: os.environ['LANGUAGE'] = orig return ret # extract information about the files def _gatherFilesInfo(self): ret = {} flags = self.header[rpm.RPMTAG_FILEFLAGS] modes = self.header[rpm.RPMTAG_FILEMODES] users = self.header[rpm.RPMTAG_FILEUSERNAME] groups = self.header[rpm.RPMTAG_FILEGROUPNAME] links = [byte_to_string(x) for x in self.header[rpm.RPMTAG_FILELINKTOS]] sizes = self.header[rpm.RPMTAG_FILESIZES] md5s = self.header[rpm.RPMTAG_FILEMD5S] mtimes = self.header[rpm.RPMTAG_FILEMTIMES] rdevs = self.header[rpm.RPMTAG_FILERDEVS] langs = self.header[rpm.RPMTAG_FILELANGS] inodes = self.header[rpm.RPMTAG_FILEINODES] requires = [byte_to_string(x) for x in self.header[rpm.RPMTAG_FILEREQUIRE]] provides = [byte_to_string(x) for x in self.header[rpm.RPMTAG_FILEPROVIDE]] files = [byte_to_string(x) for x in self.header[rpm.RPMTAG_FILENAMES]] magics = [byte_to_string(x) for x in self.header[rpm.RPMTAG_FILECLASS]] try: # rpm >= 4.7.0 filecaps = self.header[rpm.RPMTAG_FILECAPS] except AttributeError: filecaps = None # rpm-python < 4.6 does not return a list for this (or FILEDEVICES, # FWIW) for packages containing exactly one file if not isinstance(inodes, list): inodes = [inodes] if files: for idx, file in enumerate(files): pkgfile = PkgFile(file) pkgfile.path = os.path.normpath(os.path.join( self.dirName() or '/', pkgfile.name.lstrip('/'))) pkgfile.flags = flags[idx] pkgfile.mode = modes[idx] pkgfile.user = byte_to_string(users[idx]) pkgfile.group = byte_to_string(groups[idx]) pkgfile.linkto = links[idx] and os.path.normpath(links[idx]) pkgfile.size = sizes[idx] pkgfile.md5 = md5s[idx] pkgfile.mtime = mtimes[idx] pkgfile.rdev = rdevs[idx] pkgfile.inode = inodes[idx] pkgfile.requires = parse_deps(requires[idx]) pkgfile.provides = parse_deps(provides[idx]) pkgfile.lang = byte_to_string(langs[idx]) pkgfile.magic = magics[idx] if not pkgfile.magic: if stat.S_ISDIR(pkgfile.mode): pkgfile.magic = 'directory' elif stat.S_ISLNK(pkgfile.mode): pkgfile.magic = "symbolic link to `%s'" % pkgfile.linkto elif not pkgfile.size: pkgfile.magic = 'empty' if (not pkgfile.magic and not pkgfile.is_ghost and _magic): pkgfile.magic = get_magic(pkgfile.path) if pkgfile.magic is None: pkgfile.magic = '' elif Pkg._magic_from_compressed_re.search(pkgfile.magic): # Discard magic from inside compressed files ('file -z') # until PkgFile gets decompression support. We may get # such magic strings from package headers already now; # for example Fedora's rpmbuild as of F-11's 4.7.1 is # patched so it generates them. pkgfile.magic = '' if filecaps: pkgfile.filecaps = byte_to_string(filecaps[idx]) ret[pkgfile.name] = pkgfile return ret def readlink(self, pkgfile): """ Resolve symlinks for the given PkgFile, return the dereferenced PkgFile if it is found in this package, None if not. """ result = pkgfile while result and result.linkto: linkpath = urljoin(result.name, result.linkto) linkpath = os.path.normpath(linkpath) result = self.files.get(linkpath) return result def check_versioned_dep(self, name, version): # try to match name%_isa as well (e.g. 'foo(x86-64)', 'foo(x86-32)') name_re = re.compile(r'^%s(\(\w+-\d+\))?$' % re.escape(name)) for d in self.requires + self.prereq: if name_re.match(d[0]): if d[1] & rpm.RPMSENSE_EQUAL != rpm.RPMSENSE_EQUAL \ or d[2][1] != version: return False return True return False # internal function to gather dependency info used by the above ones def _gather_aux(self, header, xs, nametag, flagstag, versiontag, prereq=None): names = header[nametag] flags = header[flagstag] versions = header[versiontag] if versions: for loop in range(len(versions)): name = byte_to_string(names[loop]) evr = stringToVersion(byte_to_string(versions[loop])) if prereq is not None and flags[loop] & PREREQ_FLAG: prereq.append((name, flags[loop] & (~PREREQ_FLAG), evr)) else: xs.append(DepInfo(name, flags[loop], evr)) return xs, prereq def _gatherDepInfo(self): _requires = [] _prereq = [] _provides = [] _conflicts = [] _obsoletes = [] _recommends = [] _suggests = [] _enhances = [] _supplements = [] _requires, _prereq = self._gather_aux(self.header, _requires, rpm.RPMTAG_REQUIRENAME, rpm.RPMTAG_REQUIREFLAGS, rpm.RPMTAG_REQUIREVERSION, _prereq) _conflits, _ = self._gather_aux(self.header, _conflicts, rpm.RPMTAG_CONFLICTNAME, rpm.RPMTAG_CONFLICTFLAGS, rpm.RPMTAG_CONFLICTVERSION) _provides, _ = self._gather_aux(self.header, _provides, rpm.RPMTAG_PROVIDENAME, rpm.RPMTAG_PROVIDEFLAGS, rpm.RPMTAG_PROVIDEVERSION) _obsoletes, _ = self._gather_aux(self.header, _obsoletes, rpm.RPMTAG_OBSOLETENAME, rpm.RPMTAG_OBSOLETEFLAGS, rpm.RPMTAG_OBSOLETEVERSION) _recommends, _ = self._gather_aux(self.header, _recommends, rpm.RPMTAG_RECOMMENDNAME, rpm.RPMTAG_RECOMMENDFLAGS, rpm.RPMTAG_RECOMMENDVERSION) _suggests, _ = self._gather_aux(self.header, _suggests, rpm.RPMTAG_SUGGESTNAME, rpm.RPMTAG_SUGGESTFLAGS, rpm.RPMTAG_SUGGESTVERSION) _enhances, _ = self._gather_aux(self.header, _enhances, rpm.RPMTAG_ENHANCENAME, rpm.RPMTAG_ENHANCEFLAGS, rpm.RPMTAG_ENHANCEVERSION) _supplements, _ = self._gather_aux(self.header, _supplements, rpm.RPMTAG_SUPPLEMENTNAME, rpm.RPMTAG_SUPPLEMENTFLAGS, rpm.RPMTAG_SUPPLEMENTVERSION) return (_requires, _prereq, _provides, _conflicts, _obsoletes, _recommends, _suggests, _enhances, _supplements) def scriptprog(self, which): """ Get the specified script interpreter as a string. Depending on rpm-python version, the string may or may not include interpreter arguments, if any. """ if which is None: return '' prog = self[which] if prog is None: prog = '' elif isinstance(prog, (list, tuple)): # http://rpm.org/ticket/847#comment:2 prog = ''.join(prog) return prog def getInstalledPkgs(name): """Get list of installed package objects by name.""" pkgs = [] ts = rpm.TransactionSet() if re.search(r'[?*]|\[.+\]', name): mi = ts.dbMatch() mi.pattern('name', rpm.RPMMIRE_GLOB, name) else: mi = ts.dbMatch('name', name) for hdr in mi: pkgs.append(InstalledPkg(name, hdr)) return pkgs # Class to provide an API to an installed package class InstalledPkg(Pkg): def __init__(self, name, hdr=None): if not hdr: ts = rpm.TransactionSet() mi = ts.dbMatch('name', name) if not mi: raise KeyError(name) try: hdr = next(mi) except StopIteration: raise KeyError(name) super().__init__(name, '/', hdr, extracted=True) # create a fake filename to satisfy some checks on the filename self.filename = '%s-%s-%s.%s.rpm' % \ (self.name, self[rpm.RPMTAG_VERSION], self[rpm.RPMTAG_RELEASE], self[rpm.RPMTAG_ARCH]) def cleanup(self): pass def checkSignature(self): return (0, 'fake: pgp md5 OK') # Class to provide an API to a 'fake' package, eg. for specfile-only checks class FakePkg(AbstractPkg): def __init__(self, name, is_source=False): self.name = str(name) self.arch = None self.current_linenum = None self.dirname = None self.is_source = False # files are dictionary where key is name of a file self.files = {} self.ghost_files = {} def add_file_with_content(self, name, content, **flags): """ Add file to the FakePkg and fill the file with provided string content. """ basename = name.replace(os.path.sep, '_') path = os.path.join(self.dirName(), basename) with open(path, 'w') as out: out.write(content) pkg_file = PkgFile(name) pkg_file.path = path for key, value in flags.items(): setattr(pkg_file, key, value) self.files[name] = pkg_file def add_symlink_to(self, name, target): """ Add symlink to name file which path is related to name. Eg. name == '/etc/foo' and target == '../bar' creates a symlink file /etc/bar that points to /etc/foo. """ pkg_file = PkgFile(name) pkg_file.mode = stat.S_IFLNK pkg_file.linkto = target self.files[name] = pkg_file def readlink(self, pkgfile): # HACK: reuse the real Pkg's logic return Pkg.readlink(self, pkgfile) def dirName(self): if not self.dirname: self.__tmpdir = tempfile.TemporaryDirectory(prefix='rpmlint.%s.' % Path(self.name).name) self.dirname = self.__tmpdir.name return self.dirname def cleanup(self): if self.dirname: self.__tmpdir.cleanup() rpmlint-2.2.0+ds1/rpmlint/pkgfile.py000066400000000000000000000022551415540642600173560ustar00rootroot00000000000000import rpm class PkgFile(object): __slots__ = ['name', 'path', 'flags', 'mode', 'user', 'group', 'linkto', 'size', 'md5', 'mtime', 'rdev', 'inode', 'requires', 'provides', 'lang', 'magic', 'filecaps'] def __init__(self, name): self.name = name # Real path to the file (taking extract dir into account) self.path = name self.flags = 0 self.mode = 0 self.user = None self.group = None self.linkto = '' self.size = None self.md5 = None self.mtime = 0 self.rdev = '' self.inode = 0 self.requires = [] self.provides = [] self.lang = '' self.magic = '' self.filecaps = None @property def is_config(self): return self.flags & rpm.RPMFILE_CONFIG @property def is_doc(self): return self.flags & rpm.RPMFILE_DOC @property def is_noreplace(self): return self.flags & rpm.RPMFILE_NOREPLACE @property def is_ghost(self): return self.flags & rpm.RPMFILE_GHOST @property def is_missingok(self): return self.flags & rpm.RPMFILE_MISSINGOK rpmlint-2.2.0+ds1/rpmlint/readelfparser.py000066400000000000000000000335171415540642600205610ustar00rootroot00000000000000from itertools import dropwhile, takewhile import re import subprocess from rpmlint.helpers import ENGLISH_ENVIROMENT class ElfSection: """ A simple wrapper representing one ELF section. """ def __init__(self, name, size): self.name = name self.size = int(size, 16) class ElfProgramHeader: """ A simple wrapper representing one ELF program header. """ def __init__(self, name, flags): self.name = name self.flags = flags.replace(' ', '') class ElfDynamicSection: """ A simple wrapper representing one ELF dynamic section entry. """ def __init__(self, key, value): self.key = key self.value = value class ElfSymbol: """ A simple wrapper representing one ELF symbol. """ def __init__(self, kind, bind, visibility, name): self.type = kind self.bind = bind self.visibility = visibility self.name = name class ElfSectionInfo: """ Class contains information about ELF sections of an ELF file. The information is get with the following command line: readelf -WS. Output example: There are 12 section headers, starting at offset 0x268: Section Headers: [Nr] Name Type Address Off Size ES Flg Lk Inf Al [ 0] NULL 0000000000000000 000000 000000 00 0 0 0 [ 1] .text PROGBITS 0000000000000000 000040 000015 00 AX 0 0 1 [ 2] .rela.text RELA 0000000000000000 0001d8 000018 18 I 9 1 8 [ 3] .data PROGBITS 0000000000000000 000055 000000 00 WA 0 0 1 [ 4] .bss NOBITS 0000000000000000 000055 000000 00 WA 0 0 1 [ 5] .comment PROGBITS 0000000000000000 000055 000041 01 MS 0 0 1 [ 6] .note.GNU-stack PROGBITS 0000000000000000 000096 000000 00 0 0 1 [ 7] .eh_frame PROGBITS 0000000000000000 000098 000038 00 A 0 0 8 [ 8] .rela.eh_frame RELA 0000000000000000 0001f0 000018 18 I 9 7 8 [ 9] .symtab SYMTAB 0000000000000000 0000d0 0000f0 18 10 8 8 [10] .strtab STRTAB 0000000000000000 0001c0 000011 00 0 0 1 [11] .shstrtab STRTAB 0000000000000000 000208 000059 00 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), I (info), L (link order), O (extra OS processing required), G (group), T (TLS), C (compressed), x (unknown), o (OS specific), E (exclude), l (large), p (processor specific) """ section_regex = re.compile(r'.*\] (?P
\S*)\s*\S+\s*\S*\s*\S*\s*(?P\w*)') pic_regex = re.compile(r'\.rela?\.(data|text)') def __init__(self, path): self.path = path self.elf_files = [] self.parsing_failed_reason = None self.pic = False self.parse() def parse(self): r = subprocess.run(['readelf', '-W', '-S', self.path], encoding='utf8', stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=ENGLISH_ENVIROMENT) if r.returncode != 0: self.parsing_failed_reason = r.stderr return lines = r.stdout.splitlines() needle = 'Section Headers:' # archive files can contain multiple files i = 0 length = len(lines) while i < length: parsed_sections = [] while needle not in lines[i]: i += 1 if i == length: return # skip header and empty section i += 3 sections = [] while 'Key to Flags:' not in lines[i]: sections.append(lines[i]) i += 1 for s in sections: r = self.section_regex.search(s) section = ElfSection(r.group('section'), r.group('size')) parsed_sections.append(section) # detect a PIC section if self.pic_regex.search(section.name) is not None: self.pic = True if len(parsed_sections) > 0: self.elf_files.append(parsed_sections) class ElfProgramHeaderInfo: """ Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000040 0x0000000000400040 0x0000000000400040 0x000268 0x000268 R 0x8 INTERP 0x0002a8 0x00000000004002a8 0x00000000004002a8 0x00001c 0x00001c R 0x1 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] LOAD 0x000000 0x0000000000400000 0x0000000000400000 0x000460 0x000460 R 0x1000 LOAD 0x001000 0x0000000000401000 0x0000000000401000 0x0002ad 0x0002ad R E 0x1000 LOAD 0x002000 0x0000000000402000 0x0000000000402000 0x0001d0 0x0001d0 R 0x1000 LOAD 0x002e00 0x0000000000403e00 0x0000000000403e00 0x000230 0x000238 RW 0x1000 DYNAMIC 0x002e10 0x0000000000403e10 0x0000000000403e10 0x0001e0 0x0001e0 RW 0x8 NOTE 0x0002c4 0x00000000004002c4 0x00000000004002c4 0x000044 0x000044 R 0x4 GNU_EH_FRAME 0x002004 0x0000000000402004 0x0000000000402004 0x000054 0x000054 R 0x4 GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10 GNU_RELRO 0x002e00 0x0000000000403e00 0x0000000000403e00 0x000200 0x000200 R 0x1 """ header_regex = re.compile('\\s+(?P
\\w+)(\\s+\\w+){5}\\s+(?P[RWE ]{3}).*') def __init__(self, path): self.path = path self.headers = [] self.parsing_failed_reason = None self.parse() def parse(self): r = subprocess.run(['readelf', '-W', '-l', self.path], encoding='utf8', stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=ENGLISH_ENVIROMENT) if r.returncode != 0: self.parsing_failed_reason = r.stderr return lines = r.stdout.splitlines() needle = 'Program Headers:' while len(lines) > 0: lines = list(dropwhile(lambda x: needle not in x, lines)) # skip header lines = lines[2:] sections = list(takewhile(lambda x: x.strip() != '', lines)) for s in sections: r = self.header_regex.search(s) if r is not None: self.headers.append(ElfProgramHeader(r.group('header'), r.group('flags'))) lines = lines[len(sections):] class ElfDynamicSectionInfo: """ 0x0000000000000001 (NEEDED) Shared library: [ld-linux-x86-64.so.2] 0x000000000000000e (SONAME) Library soname: [libc.so.6] 0x000000000000000c (INIT) 0x26950 0x0000000000000019 (INIT_ARRAY) 0x1ba330 0x000000000000001b (INIT_ARRAYSZ) 16 (bytes) 0x0000000000000004 (HASH) 0x328 0x000000006ffffef5 (GNU_HASH) 0x37f8 0x0000000000000005 (STRTAB) 0x151e0 0x0000000000000006 (SYMTAB) 0x7488 0x000000000000000a (STRSZ) 24691 (bytes) 0x000000000000000b (SYMENT) 24 (bytes) 0x0000000000000003 (PLTGOT) 0x1bcbd0 0x0000000000000002 (PLTRELSZ) 1152 (bytes) 0x0000000000000014 (PLTREL) RELA 0x0000000000000017 (JMPREL) 0x24538 0x0000000000000007 (RELA) 0x1c948 0x0000000000000008 (RELASZ) 31728 (bytes) 0x0000000000000009 (RELAENT) 24 (bytes) 0x000000006ffffffc (VERDEF) 0x1c4c8 0x000000006ffffffd (VERDEFNUM) 31 0x000000000000001e (FLAGS) BIND_NOW STATIC_TLS 0x000000006ffffffb (FLAGS_1) Flags: NOW 0x000000006ffffffe (VERNEED) 0x1c918 0x000000006fffffff (VERNEEDNUM) 1 0x000000006ffffff0 (VERSYM) 0x1b254 0x000000006ffffff9 (RELACOUNT) 1232 0x0000000000000000 (NULL) 0x0 handle also: 0x60009990 (Operating System specific: 60009990) 0x24e20 0x60009991 (Operating System specific: 60009991) 0x8 """ section_regex = re.compile('\\s+\\w*\\s+\\((?P[^\\)]+)\\)\\s+(?P.*)') soname_regex = re.compile('Library soname: \\[(?P[^\\]]+)\\]') needed_regex = re.compile('Shared library: \\[(?P[^\\]]+)\\]') rpath_regex = re.compile('Library runpath: \\[(?P[^\\]]+)\\]') def __init__(self, path): self.path = path self.sections = [] self.parsing_failed_reason = None self.parse() self.parse_meta() def parse(self): r = subprocess.run(['readelf', '-W', '-d', self.path], encoding='utf8', stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=ENGLISH_ENVIROMENT) if r.returncode != 0: self.parsing_failed_reason = r.stderr return lines = r.stdout.splitlines() needle = 'Dynamic section at offset' lines = list(dropwhile(lambda x: needle not in x, lines)) # skip header lines = lines[2:] for line in lines: r = self.section_regex.search(line) self.sections.append(ElfDynamicSection(r.group('key'), r.group('value'))) def parse_meta(self): self.soname = None soname = self['SONAME'] if len(soname) == 1: r = self.soname_regex.search(soname[0]) if r: self.soname = r.group('soname') self.needed = [] for line in self['NEEDED']: r = self.needed_regex.search(line) if r: self.needed.append(r.group('library')) self.runpath = [] for line in self['RUNPATH']: r = self.rpath_regex.search(line) if r: self.runpath.append(r.group('path')) def __getitem__(self, key): return [x.value for x in self.sections if x.key == key] class ElfSymbolTableInfo: """ 7: 0000000000000000 0 SECTION LOCAL DEFAULT 7 8: 0000000000000000 0 SECTION LOCAL DEFAULT 8 9: 0000000000000000 0 SECTION LOCAL DEFAULT 6 10: 0000000000000000 18 FUNC GLOBAL DEFAULT 4 main 11: 0000000000000000 11 FUNC GLOBAL DEFAULT 5 foo ... 7: 0000000000000000 0 SECTION LOCAL DEFAULT 5 .comment 8: 0000000000000000 21 FUNC GLOBAL DEFAULT 1 main """ section_regex = re.compile('\\s+[0-9]+:\\s\\w+\\s+(\\w+)\\s+(?P\\w+)\\s+(?P\\w+)\\s+(?P\\w+)\\s+\\w+\\s+(?P\\S+)') def __init__(self, path): self.path = path self.symbols = [] self.parsing_failed_reason = None self.parse() def parse(self): try: r = subprocess.run(['readelf', '-W', '-s', self.path], encoding='utf8', stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=ENGLISH_ENVIROMENT) if r.returncode != 0: self.parsing_failed_reason = r.stderr return lines = r.stdout.splitlines() for line in lines: r = self.section_regex.search(line) if r and r.group('type') != 'SECTION': self.symbols.append(ElfSymbol(r.group('type'), r.group('bind'), r.group('visibility'), r.group('name'))) except UnicodeDecodeError as e: self.parsing_failed_reason = str(e) def get_functions_for_regex(self, regex): for sym in self.symbols: if sym.type == 'FUNC' and regex.search(sym.name): yield sym class ElfCommentInfo: """ String dump of section '.comment': [ 1] GHC 8.6.5 """ comment_regex = re.compile('\\s+\\[[\\s[0-9]+\\]\\s+(?P.*)') def __init__(self, path): self.path = path self.comments = [] self.parsing_failed_reason = None self.parse() def parse(self): r = subprocess.run(['readelf', '-p', '.comment', self.path], encoding='utf8', stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=ENGLISH_ENVIROMENT) if r.returncode != 0: self.parsing_failed_reason = r.stderr return lines = r.stdout.splitlines() for line in lines: r = self.comment_regex.search(line) if r: self.comments.append(r.group('comment')) class ReadelfParser: """ Class contains all information obtained by readelf command in a structured format. """ NOT_ELF_ERROR = 'Error: Not an ELF file - it has the wrong magic bytes at the start' so_regex = re.compile(r'/lib(64)?/[^/]+\.so(\.[0-9]+)*$') def __init__(self, pkgfile_path, path): self.is_archive = path.endswith('.a') self.is_shlib = self.so_regex.search(path) self.is_debug = path.endswith('.debug') self.section_info = ElfSectionInfo(pkgfile_path) self.program_header_info = ElfProgramHeaderInfo(pkgfile_path) self.dynamic_section_info = ElfDynamicSectionInfo(pkgfile_path) self.symbol_table_info = ElfSymbolTableInfo(pkgfile_path) self.comment_section_info = ElfCommentInfo(pkgfile_path) def parsing_failed_reason(self): reasons = [self.section_info.parsing_failed_reason, self.program_header_info.parsing_failed_reason, self.dynamic_section_info.parsing_failed_reason, self.symbol_table_info.parsing_failed_reason, self.comment_section_info.parsing_failed_reason] reasons = [r for r in reasons if r] for reason in reasons: if self.NOT_ELF_ERROR in reason: return self.NOT_ELF_ERROR return '\n'.join(reasons) if reasons else None rpmlint-2.2.0+ds1/rpmlint/rpmdiff.py000066400000000000000000000203721415540642600173640ustar00rootroot00000000000000from itertools import chain import pathlib import sys import tempfile import rpm from rpmlint.helpers import byte_to_string, print_warning from rpmlint.pkg import getInstalledPkgs, Pkg class Rpmdiff(object): # constants TAGS = (rpm.RPMTAG_NAME, rpm.RPMTAG_SUMMARY, rpm.RPMTAG_DESCRIPTION, rpm.RPMTAG_GROUP, rpm.RPMTAG_LICENSE, rpm.RPMTAG_URL, rpm.RPMTAG_PREIN, rpm.RPMTAG_POSTIN, rpm.RPMTAG_PREUN, rpm.RPMTAG_POSTUN, rpm.RPMTAG_PRETRANS, rpm.RPMTAG_POSTTRANS) PRCO = ('REQUIRES', 'PROVIDES', 'CONFLICTS', 'OBSOLETES', 'RECOMMENDS', 'SUGGESTS', 'ENHANCES', 'SUPPLEMENTS') # {fname : (size, mode, mtime, flags, dev, inode, # nlink, state, vflags, user, group, digest)} __FILEIDX = [['S', 0], ['M', 1], ['5', 11], ['D', 4], ['N', 6], ['L', 7], ['V', 8], ['U', 9], ['G', 10], ['F', 3], ['T', 2]] DEPFORMAT = '%-12s%s %s %s %s' FORMAT = '%-12s%s' ADDED = 'added' REMOVED = 'removed' def __init__(self, old, new, ignore=None, exclude=None): self.result = [] self.ignore = ignore or [] self.exclude = exclude or [] FILEIDX = self.__FILEIDX for tag in self.ignore: for entry in FILEIDX: if tag == entry[0]: entry[1] = None break try: old = self.__load_pkg(old).header new = self.__load_pkg(new).header except KeyError as e: print_warning(str(e)) sys.exit(2) # Compare single tags for tag in self.TAGS: old_tag = old[tag] new_tag = new[tag] if old_tag != new_tag: tagname = rpm.tagnames[tag] if old_tag is None: self.__add(self.FORMAT, (self.ADDED, tagname)) elif new_tag is None: self.__add(self.FORMAT, (self.REMOVED, tagname)) else: self.__add(self.FORMAT, ('S.5.....', tagname)) # compare Provides, Requires, ... for tag in self.PRCO: self.__comparePRCOs(old, new, tag) # compare the files old_files_dict = self.__fileIteratorToDict(old.fiFromHeader()) new_files_dict = self.__fileIteratorToDict(new.fiFromHeader()) files = list(set(chain(iter(old_files_dict), iter(new_files_dict)))) files.sort() for f in files: if self._excluded(f): continue diff = False old_file = old_files_dict.get(f) new_file = new_files_dict.get(f) if not old_file: self.__add(self.FORMAT, (self.ADDED, f)) elif not new_file: self.__add(self.FORMAT, (self.REMOVED, f)) else: fmt = '' for entry in FILEIDX: if entry[1] is not None and \ old_file[entry[1]] != new_file[entry[1]]: fmt += entry[0] diff = True else: fmt += '.' if diff: self.__add(self.FORMAT, (fmt, f)) def _excluded(self, f): f = pathlib.PurePath(f) for glob in self.exclude: if f.match(glob): return True if glob.startswith('/'): for parent in f.parents: if parent.match(glob): return True return False # return a report of the differences def textdiff(self): return '\n'.join((fmt % data for fmt, data in self.result)) # do the two rpms differ def differs(self): return bool(self.result) # add one differing item def __add(self, fmt, data): self.result.append((fmt, data)) # load a package from a file or from the installed ones def __load_pkg(self, name): # FIXME: redo to try file/installed and proceed based on that, or pick # one of the selected first tmpdir = tempfile.gettempdir() try: if name.is_file(): return Pkg(name, tmpdir) except TypeError: pass inst = getInstalledPkgs(str(name)) if not inst: raise KeyError(f'No installed packages by name {name}') if len(inst) > 1: raise KeyError(f'More than one installed packages by name {name}') return inst[0] # output the right string according to RPMSENSE_* const def sense2str(self, sense): s = '' for tag, char in ((rpm.RPMSENSE_LESS, '<'), (rpm.RPMSENSE_GREATER, '>'), (rpm.RPMSENSE_EQUAL, '=')): if sense & tag: s += char return s # output the right requires string according to RPMSENSE_* const def req2str(self, req): s = 'REQUIRES' # we want to use 64 even with rpm versions that define RPMSENSE_PREREQ # as 0 to get sane results when comparing packages built with an old # (64) version and a new (0) one if req & (rpm.RPMSENSE_PREREQ or 64): s = 'PREREQ' ss = [] if req & rpm.RPMSENSE_SCRIPT_PRE: ss.append('pre') if req & rpm.RPMSENSE_SCRIPT_POST: ss.append('post') if req & rpm.RPMSENSE_SCRIPT_PREUN: ss.append('preun') if req & rpm.RPMSENSE_SCRIPT_POSTUN: ss.append('postun') if req & getattr(rpm, 'RPMSENSE_PRETRANS', 1 << 7): # rpm >= 4.9.0 ss.append('pretrans') if req & getattr(rpm, 'RPMSENSE_POSTTRANS', 1 << 5): # rpm >= 4.9.0 ss.append('posttrans') if ss: s += '(%s)' % ','.join(ss) return s # compare Provides, Requires, Conflicts, Obsoletes def __comparePRCOs(self, old, new, name): try: oldflags = old[name[:-1] + 'FLAGS'] except ValueError: # assume tag not supported, e.g. Recommends with older rpm return newflags = new[name[:-1] + 'FLAGS'] # fix buggy rpm binding not returning list for single entries if not isinstance(oldflags, list): oldflags = [oldflags] if not isinstance(newflags, list): newflags = [newflags] o = zip(old[name], oldflags, old[name[:-1] + 'VERSION']) if not isinstance(o, list): o = list(o) n = zip(new[name], newflags, new[name[:-1] + 'VERSION']) if not isinstance(n, list): n = list(n) # filter self provides, TODO: self %name(%_isa) as well if name == 'PROVIDES': oldE = old['epoch'] is not None and str(old['epoch']) + ':' or '' oldV = '%s%s' % (oldE, old.format('%{VERSION}-%{RELEASE}')) oldNV = (old['name'], rpm.RPMSENSE_EQUAL, oldV.encode()) newE = new['epoch'] is not None and str(new['epoch']) + ':' or '' newV = '%s%s' % (newE, new.format('%{VERSION}-%{RELEASE}')) newNV = (new['name'], rpm.RPMSENSE_EQUAL, newV.encode()) o = [entry for entry in o if entry != oldNV] n = [entry for entry in n if entry != newNV] for oldentry in o: if oldentry not in n: namestr = name if namestr == 'REQUIRES': namestr = self.req2str(oldentry[1]) self.__add(self.DEPFORMAT, (self.REMOVED, namestr, byte_to_string(oldentry[0]), self.sense2str(oldentry[1]), byte_to_string(oldentry[2]))) for newentry in n: if newentry not in o: namestr = name if namestr == 'REQUIRES': namestr = self.req2str(newentry[1]) self.__add(self.DEPFORMAT, (self.ADDED, namestr, byte_to_string(newentry[0]), self.sense2str(newentry[1]), byte_to_string(newentry[2]))) def __fileIteratorToDict(self, fi): result = {} for filedata in fi: result[filedata[0]] = filedata[1:] return result rpmlint-2.2.0+ds1/rpmlint/spellcheck.py000066400000000000000000000106511415540642600200510ustar00rootroot00000000000000import re from rpmlint.helpers import print_warning try: from enchant import Broker from enchant.checker import SpellChecker from enchant.tokenize import EmailFilter, URLFilter, WikiWordFilter ENCHANT = True except ImportError: # if the enchant is not present we simply continue but without # spellchecking work being done ENCHANT = None class Spellcheck(object): """ The object containing current state of spellchecking used within rpmlint """ sentence_break_regex = re.compile(r'(^|[.:;!?])\s*$') _enchant_checkers = {} def __init__(self): pass def _init_checker(self, lang='en_US'): """ Initialize a checker of selected language if it is not yet present lang: language to initialize the dictionary """ # C language means English if lang == 'C': lang = 'en_US' # test if we actually have working enchant if not ENCHANT: print_warning('(none): W: unable to init enchant, spellchecking disabled.') return # there might not be myspell/aspell/etc dicts present broker = Broker() if not broker.dict_exists(lang): print_warning(f'(none): W: unable to load spellchecking dictionary for {lang}.') return if lang not in self._enchant_checkers: checker = SpellChecker(lang, filters=[EmailFilter, URLFilter, WikiWordFilter]) self._enchant_checkers[lang] = checker def spell_check(self, text, fmt, lang='en_US', pkgname='', ignored_words=None): """ Spell check string and return list of warnings if we found out any typos. text: the checked text fmt: format of the result ie 'Description({})' lang: language code ie en_US, default en_US pkgname: name of the checked package - for specific ignore finegraining ignored_words: words to be ignored by the spellchecker """ warned = set() suggestions = {} # Initialize spelling dictionary if not already done if lang not in self._enchant_checkers: self._init_checker(lang) # If the init failed, just return if lang not in self._enchant_checkers: return suggestions checker = self._enchant_checkers[lang] if checker: checker.set_text(re.sub(r'\s+', ' ', text)) # Uppercase packagename to be case insensitive uppername = pkgname.upper() # Allow partial matches for just part of the name upperparts = uppername.split('-') # In english we can have ie. django's (so ignore such words) if lang.startswith('en'): ups = [x + "'S" for x in upperparts] upperparts.extend(ups) # uppercase all ignorewords if ignored_words: ignored_words = [x.upper() for x in ignored_words] # for each error found skip some parts for err in checker: # Skip already warned if err.word in warned: continue warned.add(err.word) # Skip all capitalized words that do not start a sentence if err.word[0].isupper() and not \ self.sentence_break_regex.search(checker.leading_context(3)): continue # Skip all uppercase words upperword = err.word.upper() if err.word == upperword: continue # skip all ignored words if ignored_words and upperword in ignored_words: continue # Skip errors containing package name or equal to a # 'component' of it, case insensitively if upperword in uppername or upperword in upperparts: continue # Work around enchant's digit tokenizing behavior where # we split on numbers, just ommit everything thats in there if checker.leading_context(1).isdigit() or \ checker.trailing_context(1).isdigit(): continue # Warn and suggest sug = ', '.join(checker.suggest()[:3]) if sug: sug = f'-> {sug}' suggestions[err.word] = fmt.format(lang) + f' {err.word} {sug}' return suggestions rpmlint-2.2.0+ds1/rpmlint/stringsparser.py000066400000000000000000000012501415540642600206350ustar00rootroot00000000000000import subprocess from rpmlint.helpers import ENGLISH_ENVIROMENT class StringsParser: """ Class contains all information obtained by strings command. """ def __init__(self, pkgfile_path): self.pkgfile_path = pkgfile_path self.strings = [] self.parsing_failed_reason = None self.parse() def parse(self): r = subprocess.run(['strings', self.pkgfile_path], encoding='utf8', stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=ENGLISH_ENVIROMENT) if r.returncode != 0: self.parsing_failed_reason = r.stderr return self.strings = r.stdout.splitlines() rpmlint-2.2.0+ds1/rpmlint/version.py000066400000000000000000000003751415540642600174230ustar00rootroot00000000000000try: from importlib.metadata import version, PackageNotFoundError except ImportError: from importlib_metadata import version, PackageNotFoundError try: __version__ = version('rpmlint') except PackageNotFoundError: __version__ = '0.0.0' rpmlint-2.2.0+ds1/setup.cfg000066400000000000000000000003261415540642600155140ustar00rootroot00000000000000[metadata] license_file = COPYING [aliases] test=pytest [flake8] ignore = E122,E501,W504 import-order-style = google application-import-names = Testing [tool:pytest] addopts = -vv --cov=rpmlint -n auto --flake8 rpmlint-2.2.0+ds1/setup.py000077500000000000000000000043161415540642600154130ustar00rootroot00000000000000from setuptools import setup setup( name='rpmlint', description='Check for common errors in RPM packages', long_description=('Rpmlint is a tool to check common errors in RPM packages.' 'Binary and source packages can be checked'), url='https://github.com/rpm-software-management/rpmlint', download_url='https://github.com/rpm-software-management/rpmlint', version='2.2.0', author='Frédéric Lepied', author_email='flepied@mandriva.com', maintainer='RPMLint maintainers', maintainer_email='rpm-ecosystem@lists.rpm.org', license='License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)', classifiers=[ # complete classifier list: # http://pypi.python.org/pypi?%3Aaction=list_classifiers 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)', 'Operating System :: Unix', 'Operating System :: POSIX', 'Operating System :: POSIX :: Linux', 'Programming Language :: Python', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Topic :: Utilities', 'Topic :: Software Development :: Quality Assurance', 'Topic :: System :: Archiving :: Packaging', ], platforms=['Linux'], keywords=['RPM', '.spec', 'validator'], install_requires=[ 'pybeam', 'pyxdg', 'rpm', 'toml', 'zstd', 'importlib-metadata;python_version<"3.8"', ], tests_require=[ 'pytest', 'pytest-cov', 'pytest-flake8', 'pytest-xdist', ], packages=[ 'rpmlint', 'rpmlint.checks', ], package_data={ 'rpmlint': ['configdefaults.toml'], 'rpmlint.descriptions': ['*.toml'], }, include_package_data=True, entry_points={ 'console_scripts': [ 'rpmdiff = rpmlint.cli:diff', 'rpmlint = rpmlint.cli:lint', ], }, ) rpmlint-2.2.0+ds1/test/000077500000000000000000000000001415540642600146515ustar00rootroot00000000000000rpmlint-2.2.0+ds1/test/Testing.py000066400000000000000000000033201415540642600166360ustar00rootroot00000000000000import glob import os from pathlib import Path import platform import re import shutil import subprocess from rpmlint.config import Config from rpmlint.pkg import FakePkg, Pkg import rpmlint.spellcheck def testpath(): return Path(os.environ.get('TESTPATH', Path(__file__).parent)) TEST_CONFIG = [testpath() / 'configs/test.config'] CONFIG = Config(TEST_CONFIG) # predicates used for pytest.mark.skipif decorators IS_X86_64 = platform.machine() == 'x86_64' IS_I686 = re.match(platform.machine(), 'i[3456]86') HAS_32BIT_GLIBC = glob.glob('/lib/ld-linux.so.*') HAS_CHECKBASHISMS = shutil.which('checkbashisms') HAS_DASH = shutil.which('dash') HAS_DESKTOP_FILE_UTILS = shutil.which('desktop-file-validate') HAS_APPSTREAM_GLIB = shutil.which('appstream-util') RPMDB_PATH = subprocess.run(['rpm', '--eval', '%_dbpath'], encoding='utf8', stdout=subprocess.PIPE).stdout HAS_RPMDB = RPMDB_PATH and Path(RPMDB_PATH.strip()).exists() def _has_dictionary(language): if not rpmlint.spellcheck.ENCHANT: return False spell = rpmlint.spellcheck.Spellcheck() spell._init_checker(language) return spell._enchant_checkers.get(language) HAS_ENGLISH_DICTIONARY = _has_dictionary('en_US') HAS_CZECH_DICTIONARY = _has_dictionary('cs_CZ') def get_tested_path(path): return testpath() / path def get_tested_package(name, testdir): filename = Path(name).name + '-*.rpm' candidates = list(get_tested_path(name).parent.glob(filename)) assert len(candidates) == 1 return Pkg(candidates[0], testdir) def get_tested_spec_package(name): filename = Path(name).name + '.spec' candidates = list(get_tested_path(name).parent.glob(filename)) assert len(candidates) == 1 return FakePkg(candidates[0]) rpmlint-2.2.0+ds1/test/configs/000077500000000000000000000000001415540642600163015ustar00rootroot00000000000000rpmlint-2.2.0+ds1/test/configs/broken.config000066400000000000000000000000721415540642600207470ustar00rootroot00000000000000Distribution = [ "Fedora Project" Vendor = Fedora Project rpmlint-2.2.0+ds1/test/configs/descriptions.config000066400000000000000000000004321415540642600221750ustar00rootroot00000000000000[Descriptions] no-binary = """ A new text for no-binary error. """ no-soname = """ A new text for no-soname error. """ non-standard-dir-in-usr = """ A new text for non-standard-dir-in-usr error. """ non-standard-dir-in-var = """ A new text for non-standard-dir-in-var error. """ rpmlint-2.2.0+ds1/test/configs/test.config000066400000000000000000000025401415540642600204500ustar00rootroot00000000000000Distribution = "Fedora Project" Vendor = "Fedora Project" MandatoryOptflags = ['-fno-PIE', '-g', '-Ofast'] ForbiddenOptflags = ['-frounding-math'] [WarnOnFunction.crypto-policy-non-compliance-openssl] f_name = "SSL_CTX_set_cipher_list" description = '''This application package calls a function to explicitly set crypto ciphers for SSL/TLS. That may cause the application not to use the system-wide set cryptographic policy and should be modified in accordance to: https://fedoraproject.org/wiki/Packaging:CryptoPolicies''' [WarnOnFunction.crypto-policy-non-compliance-gnutls-1] f_name = "gnutls_priority_set_direct" description = '''This application package calls a function to explicitly set crypto ciphers for SSL/TLS. That may cause the application not to use the system-wide set cryptographic policy and should be modified in accordance to: https://fedoraproject.org/wiki/Packaging:CryptoPolicies''' [WarnOnFunction.crypto-policy-non-compliance-gnutls-2] f_name = "gnutls_priority_init" good_param = "SYSLOG" description = '''This application package calls a function to explicitly set crypto ciphers for SSL/TLS. That may cause the application not to use the system-wide set cryptographic policy and should be modified in accordance to: https://fedoraproject.org/wiki/Packaging:CryptoPolicies''' rpmlint-2.2.0+ds1/test/configs/test.override.config000066400000000000000000000000421415540642600222610ustar00rootroot00000000000000ValidGroups = [ 'Only/One', ] rpmlint-2.2.0+ds1/test/configs/test2.config000066400000000000000000000003111415540642600205240ustar00rootroot00000000000000Vendor = "SUSE" ExtraMenuNeeds = [ 'windows', ] [WarnOnFunction.crypto-policy-non-compliance-openssl] f_name = "REPLACED" good_param = "ADDED" [WarnOnFunction.crypto-policy-3] f_name = "new_blobie" rpmlint-2.2.0+ds1/test/configs/testfilters.config000066400000000000000000000010721415540642600220400ustar00rootroot00000000000000Filters = [ '.*invalid-buildhost.*', '.*executable-in-library-package.*', '.*non-versioned-file-in-library-package.*', '.*shlib-policy-name-error.*', # this is just a comment to be ignored by the parsing '.*hardcoded-path-in-buildroot-tag.*', '.*no-buildroot-tag.*', '.*cross-directory-hard-link.*', 'no-regex', ' no-regex-with-leading-space', 'ngircd.*: E: bad-error', '^ngircd.*: E: test-color-error details of the error$', 'fatal-error' ] BlockedFilters = [ 'fatal-error' ] [Scoring] test-color-error = 12345 rpmlint-2.2.0+ds1/test/configs/testing-rpmlintrc000066400000000000000000000156341415540642600217220ustar00rootroot00000000000000# This line is mandatory to access the configuration functions from Config import * setBadness('suse-dbus-unauthorized-service', 0) setBadness('suse-other-error','20') setBadness ('suse-other-error-123','200') # # Output filters addFilter(r"arch-independent-package-contains-binary-or-object ") addFilter('.*arch-independent-package-contains-binary-or-object.*/boot/vc/.*.elf') addFilter("class-path-in-manifest") addFilter("deprecated-grep") addFilter(".*desktopfile-without-binary.*") addFilter("desktopfile-without-binary") addFilter('devel-dependency') addFilter('devel-dependency python-devel') addFilter('devel-dependency python2-devel') # These are binaries not executed on the CPU but on the graphics card addFilter('devel-dependency python3-devel') addFilter("devel-file-in-non-devel-package") addFilter("devel-file-in-non-devel-package .*/clang/.*/include/.*") addFilter("devel-file-in-non-devel-package .*/clang/.*/lib/.*") addFilter('devel-file-in-non-devel-package .*/commands/src/compiler.c') addFilter('devel-file-in-non-devel-package .*/Cython/.*') addFilter("devel-file-in-non-devel-package .*/lib.*/*.a") addFilter("devel-file-in-non-devel-package .*/lib.*/*.so") addFilter("devel-file-in-non-devel-package .*/site-packages/cffi/_cffi_include.h") addFilter("devel-file-in-non-devel-package .*/site-packages/cffi/_embedding.h") addFilter("devel-file-in-non-devel-package .*/site-packages/cffi/parse_c_type.h") addFilter('devel-file-in-non-devel-package .*/tests/.*') addFilter(".*no-manual-page-for-binary.*") addFilter("devel-file-in-non-devel-package .*/usr/include/.*") addFilter("devel-file-in-non-devel-package .* /usr/lib.*/liblftp-.*.so") addFilter('devel-file-in-non-devel-package .*/wcs/.*') addFilter('doc-file-dependency') addFilter("doc-file-dependency .*") addFilter("doc-file-dependency .*\.py ") addFilter("doc-file-dependency .*\.py ") addFilter("E: devel-file-in-non-devel-package .*site-packages.*fake_libc_include.*") addFilter("E: devel-file-in-non-devel-package") addFilter("E: shlib-policy-name-error") addFilter("executable-stack") addFilter("explicit-lib-dependency") addFilter("explicit-lib-dependency libgobject-2_0-0") addFilter("explicit-lib-dependency .*libmpv1") addFilter("explicit-lib-dependency libqt5_sql_backend") addFilter("explicit-lib-dependency .*-tblib") addFilter("file-contains-date-and-time") addFilter("files-duplicate.*/usr/share/doc/packages") addFilter("hidden-file-or-dir") addFilter("hidden-file-or-dir /usr/lib/python.*/site-packages/tldextract/.tld_set_snapshot") addFilter("incoherent-init-script-name quasselcore") addFilter("incoherent-init-script-name raw") addFilter("incoherent-logrotate-file /etc/logrotate.d/quasselcore") addFilter("incorrect-fsf-address") addFilter("init-script-without-%restart_on_update-postun /etc/init.d/raw") addFilter("init-script-without-%stop_on_removal-preun /etc/init.d/raw") addFilter("libguilereadline.* devel-file-in-non-devel-package") addFilter("incoherent-init-script-name quasselcore") addFilter("libvlccore.* shared-lib-calls-exit") addFilter("missing-PT_GNU_STACK-section") addFilter("name-repeated-in-summary") addFilter("net-snmp-devel.* files-duplicate.*man.*") addFilter("net-snmp.*incoherent-init-script-name") addFilter("no-binary") addFilter("no-dependency-on libffmpeg.*") addFilter(".*no-manual-page-for-binary.*") # /usr/local/bin/dib-python interpreter is only used inside # the generated image so it's not something that affects the # host or the package itself. addFilter("no-manual-page-for-binary") addFilter(".* no-manual-page-for-binary mitm.*") addFilter(".* no-manual-page-for-binary path.*") addFilter("no-manual-page-for-binary rst.*") addFilter("no-manual-page-for-binary .*subunit.*") addFilter("non-conffile-in-etc /etc/rpm/macros.python3") addFilter("non-conffile-in-etc /etc/sysconfig/SuSEfirewall2.d/services/quassel") addFilter("non-etc-or-var-file-marked-as-conffile") addFilter('non-executable-script') addFilter("non-executable-script") addFilter("non-executable-script.*/usr/lib/python.*/site-packages/ansible/(cli|galaxy|module_utils|plugins/action|runner|utils)/.*.py"); addFilter("non-executable-script.*/usr/lib/python.*/site-packages/ansible/modules/.*"); addFilter(".*non-executable-script.*/usr/lib/python2.7/site-packages/diskimage_builder/lib/.*") addFilter("non-standard-uid /var/lib/quasselcore quasslecore") addFilter("non-standard-uid /var/log/quassel quasslecore") addFilter("no-reload-entry /etc/init.d/raw") addFilter(".*obsolete-not-provided.*") addFilter("pem-certificate") addFilter("pem-certificate .*/site-packages/distributed/tests/.*\.pem") addFilter("perl.* devel-file-in-non-devel-package") addFilter("perl-SNMP.* zero-length.*\.bs") addFilter("python-naming-policy-not-applied") addFilter("pyzmq-devel.*: W: no-dependency-on python\(abi\)") addFilter('ratbagd.* suse-dbus-unauthorized-service.*') addFilter("script-without-shebang") addFilter("shlib-fixed-dependency") addFilter("strict-aliasing-punning") addFilter("unstripped-binary-or-object") addFilter ("W: devel-file-in-non-devel-package") addFilter("W: files-attr-not-set") addFilter(".*W:.*files-duplicate.*/pam/su.*/pam.d/su-l.*") addFilter("W: files-duplicate /usr/share/x11vnc/classes/index.vnc /usr/share/x11vnc/classes/ssl/index.vnc") addFilter("W: hidden-file-or-dir /usr/lib/python3.*/site-packages/openlp/.version") addFilter("W: no-binary"); addFilter("W: obsolete-suse-version-check.*") addFilter("W: patch-not-applied Patch5: x11vnc-thread-auth.diff") addFilter(".*W:.*permissions-symlink.*/bin/mount.*") addFilter(".*W:.*permissions-symlink.*/bin/su.*") addFilter(".*W:.*permissions-symlink.*/bin/umount.*") addFilter("W: python-naming-policy-not-applied .*") addFilter("W: python-naming-policy-not-applied") addFilter("wrong-file-end-of-line-encoding") addFilter("wrong-file-end-of-line-encoding /usr/share/doc/packages/antlr-manual/examples/") addFilter(".*wrong-script-interpreter.*/usr/lib/python2.7/site-packages/diskimage_builder/elements/.*") addFilter("W: shlib-policy-missing-suffix") addFilter("W: suse-branding-unversioned-requires") addFilter(".*ZEO/tests/.*pem$") addFilter("zero-length /usr/lib64/python3.3/test/namespace_pkgs/module_and_namespace_package/a_test/empty") addFilter("zero-length /usr/lib64/python3.3/test/nullcert.pem") addFilter("zero-length /usr/share/doc/packages/antlr-manual/examples/csharp/csharp_v1/testfiles/Empty.cs") addFilter("zero-length /usr/share/doc/packages/python-alembic-doc/html/_static/site_custom_css.css") addFilter("zlib-devel.*: W: no-dependency-on zlib*/zlib-libs/libzlib") addFilter("zlib.src.*: W: make-check-outside-check-section time make check") # These are binaries not executed on the CPU but on the graphics card addFilter('.*arch-independent-package-contains-binary-or-object.*/boot/vc/.*.elf') # These are binaries not executed on the CPU but on the graphics card addFilter('.*arch-independent-package-contains-binary-or-object.*/boot/vc/.*.elf') import os import time print(time.time()) print(os.geteuid()) print("I can do anyting as the above user") rpmlint-2.2.0+ds1/test/configs/testing2-rpmlintrc000066400000000000000000000001651415540642600217750ustar00rootroot00000000000000addFilter('I am not used') addFilter('He is not used') # addFilter('She is not used') addFilter('no-%build-section') rpmlint-2.2.0+ds1/test/configs/testing3-rpmlintrc000066400000000000000000000003301415540642600217700ustar00rootroot00000000000000# Backward compatibility should match spaces within paren addFilter('no-spaces-in-paren') addFilter( 'has-spaces-in-paren' ) addFilter( 'multiple-spaces-in-paren' ) addFilter("doublequotes-instead-of-singlequotes")rpmlint-2.2.0+ds1/test/configs/testlists1.config000066400000000000000000000001461415540642600216100ustar00rootroot00000000000000Filters = [ '.*invalid-buildhost.*', ] ValidGroups = [ 'bullshitgroup', 'group2/wrong', ] rpmlint-2.2.0+ds1/test/configs/testlists2.config000066400000000000000000000001351415540642600216070ustar00rootroot00000000000000Filters = [ '.*cross-directory-hard-link.*', ] ValidGroups = [ 'System/Libraries', ] rpmlint-2.2.0+ds1/test/dump_stats.py000066400000000000000000000004101415540642600174010ustar00rootroot00000000000000if __name__ == '__main__': import sys import pstats p = pstats.Stats(sys.argv[1]) N = 60 p.sort_stats('cumulative').print_stats(N) print('========================================================') p.sort_stats('ncalls').print_stats(N) rpmlint-2.2.0+ds1/test/rpmlintrc/000077500000000000000000000000001415540642600166635ustar00rootroot00000000000000rpmlint-2.2.0+ds1/test/rpmlintrc/multiple/000077500000000000000000000000001415540642600205165ustar00rootroot00000000000000rpmlint-2.2.0+ds1/test/rpmlintrc/multiple/sample-rpmlintrc000066400000000000000000000000431415540642600237270ustar00rootroot00000000000000addFilter("W: files-attr-not-set") rpmlint-2.2.0+ds1/test/rpmlintrc/multiple/sample.rpmlintrc000066400000000000000000000000601415540642600237270ustar00rootroot00000000000000setBadness('suse-dbus-unauthorized-service', 0) rpmlint-2.2.0+ds1/test/rpmlintrc/multiple/sample.spec000066400000000000000000000002111415540642600226450ustar00rootroot00000000000000Name: sample Version: 0 Release: 0 License: GPL-2.0-only Summary: Whatever %description Whatever. rpmlint-2.2.0+ds1/test/rpmlintrc/single/000077500000000000000000000000001415540642600201445ustar00rootroot00000000000000rpmlint-2.2.0+ds1/test/rpmlintrc/single/sample.rpmlintrc000066400000000000000000000000601415540642600233550ustar00rootroot00000000000000setBadness('suse-dbus-unauthorized-service', 0) rpmlint-2.2.0+ds1/test/rpmlintrc/single/sample.spec000066400000000000000000000002111415540642600222730ustar00rootroot00000000000000Name: sample Version: 0 Release: 0 License: GPL-2.0-only Summary: Whatever %description Whatever. rpmlint-2.2.0+ds1/test/source/000077500000000000000000000000001415540642600161515ustar00rootroot00000000000000rpmlint-2.2.0+ds1/test/source/CheckInclude-1-1.src.rpm000066400000000000000000000146511415540642600223030ustar00rootroot00000000000000CheckInclude-1-1t>d ,0@D acb9ab0c88143faf5967beb61e5726d36188651da3N_ e_t>.?d  04@DJ r~     (,889<:PFaGxHI\]^defCCheckInclude11None hereCheckInclude test.Z>buildboxGPLv2Van de Bugger UnspecifiedCheckInclude.incCheckInclude-1.tar.gzhttp://example.com/linuxx86_64|ZUZ{7Z-c556711a7e98e865c1e6bd0fa149c8b0c927d6a2e2220a9fe78fd7a80c00ffeac5bfda1e3cb6a7fda052b3823cfcdbcba2fb1777d45dbd3c73946854dcf00c00f6a6b72552c1b59d8cdff5a5fb7da93219ebe847a283b2b9cf7dbe5ae50995fc vdbvdbvdbvdbvdbvdb  rpmlib(CompressedFileNames)rpmlib(FileDigests)3.0.4-14.6.0-14.13.0.1Vj@Van de Bugger - 1.1- First release.buildbox 1510181694CheckInclude-1.tar.gzCheckInclude.incCheckInclude.speccpiogzip9utf-8? SkA^ Mfwc4VF4l^flΖjYтG/^ăxZA қ`n~4&j;μ{OqQhzF )фqM jnxI`*7rkǻ'Gވ܁ '^kRĿyܝW7[|_C/>`inY09Q/g]|~mpc'/EmiH ni7bLaT*~mj#;NCjjslиjBE/-utbu%=k*эdJX=lzEF%{!1f'#XUMA#fK5{pF#oJ[glta6P Fuy.f0#}KJ El 6 9\HՍ2j&i|qJ_** &C~yE7iʪxMA0%8 Uka:mDVY뎚jU UN+%,{sfʭVbKx )lp ec5b42385296a4fb2dcd160cafea651d5d671192ddfe363e3fa6a9fc25ab24478502d26da0baf75e4af724db9dde6fcaa0de25d1vjя3ۆ<>-t?dd  "48DH NXg    : <DN\dl(FGHIR\]^de f`Cinvalid-spec-name00None hereSpecCheck test 2.^5localhost GPLv2UndefinedSource0.tar.gzhttp://rpmlint.zarb.org/#invalid-spec-namelinuxx86_643큤^01^5b940cfe91abf3d568a27fbe902ea1c8b46ace342c766d3d981a5e74b228162ff2b4e5ed2bd14cfd117aeb7146dbc212a76b1aa9714f83c6ab8e14aee33ee9111 shubshubusersusers  rpmlib(CompressedFileNames)rpmlib(FileDigests)3.0.4-14.6.0-14.15.1localhost 1590572507Source0.tar.gzSpecCheck5.speccpiogzip9utf-87b60f284e191f5486c30ced0d1320ab626e3d440350200eea58357f084350bfa?0C.LХ۶m۶u۶m۶m۶mܶm?1Y̳ɬ8y*##rQlČ12a7g3&W#g / @xm69߫ g_F—lBUӖ6_U`e*W&vlgEqP,q%\2ĮdߗQru;a}:kwn(>L Ixk$H1e>o xև&LKs!POaY5'M4CAQ g !L!k׽={,I8>5IN]=8+)V ~YS-zѳ3]E&~Lx;]H5yv/-??o/֯!EbaoϭoǯCS/a]\Ŧ2욅D αo`Xuy7ϝ[p!2 |:ϫs~UC}tD\kV_u؃w/a_ع_mlgT¢SnobDħ{`߂_ +q}:8ȿ}eGvE c*O _; #N~ove??h]HRo$d~R tCļD< ,ys:| w~ %o]l aA>=me 8 gnҎ: Iڥx6WDNS!,QF9 -EUaAJ(X!V MW95-M 0'Q4>ūt2ڛA' :o!:"5a-ߢAdDaA}IZiSHP%H#X%XZ4 ,kUn'5zjY Liw(dF*-X5Av rkI0Q(o;%24&бA1q)Ts-*0WRq8˅DޫqLh 4(0aDul!~}@A%Τwa< QՋYR)69["p/(&y?:(L@.5{ 7B|vڔT#oAt" M_梀: ꫵXJp `BX|aa;4a f Jp|NǬ #X9 ,zh" n%М|Z[CRsmM (K^T"a4{/:/_5JUe Wu@E0|̩@X^^XL_؛qwˋyZ_z?!hNÁY9@)*@"yftvgCW}ZmbcCJ ^N?YBA5_Z&ܟT>LzG {V 2&Q&u $"p5)C ^2^ΣYEMgGli:nCBvKDɗ+S/ϣC$ċx@ G .pRka3 X&^SCHp J0 2O'jւL)$rӁ,[@,qkN2h毃?  Gw#BL#ql(@~+x2}{ғx kx7[߃;!s^d#+*lCh]On/yDYD츜cСN?YAXemkIdLQu=*||][[svySa.˻I #pDž(k %hx}F{pc_T\p4mnHy8DV=UH(GT+Uo8j;w`ooDٱ5=JwhǘJ! ^Zb0z X!Tn:\ r/ļ!iFz[3Cģtx-QQ L]c]Uzx Wx Xރ!530nXG lHؐ =ífN.|},2 PWu>WOW)w-81 L :,Qx~B*> Ս۫67VNPNlЈq/AAMC|͞zUz+Պ*f-UtU/ ܿKh1뎮0)>!ظg c}a2`50gyUf0HekcHY?-Ag6~IxROZwδ_H:~+`FCqE:,O7Үy:;2f(̬KL!su52l kxxt;;oK=Q(;⠌*1"]3P,LOD_="f;FHrZ)^!-mk0-LJaN7RKxeLo՚~r6B+.ξ ~KAQӡF~vRg5t2q뺄oQmr`+fQd@ujN? ;U1teC)'i%1$K T}nܾ ջbM$7B4'2kHeLv"O.?F2}#o -2\T^!7Q#2 f3R]))H|ok1龶]f -nVye/%njZ.yzH7Ŕ]&PSdSI&O|EӺH E[/ɇ/)`jSq߳@L+ [l+hLmpNE@`]ATڋ0ڸک8ZZa/EbT"PDb/51t7x"AjʓN[gGXx{ "%[]Z{O=IӞzU騅YAf:34jFѡO[-[x[/[_E$N҈ 㠳|-{'kChC50nU=`4'7(`rfȎsHI |Uu@jeAdrw>5=">#@b3L{;ԦU QrVBҎGpC6J4MĦ&6h@cSBﰢceY4DpjS ݦJJ2kS~,Z'UIVm1tp}vJsuj ?Ѱ\S]+KCfWM͹:|A>$"q?ag .% A((=Hj` Tra[D7RF0אNY@yHCC\H jG$H̟J;;Sq{&3 yЯ6%ׁ-7y@p~`5y q4XxNك Kmg䥦ԣ \m>!Lº{%;:Q9!lH 4c,cZ~[!UK|K%%#򍬌KVDnU? 1P^_%N1ҽhW.7{s&$;+}S`geu.TeU5ɲ'*|heR{G-HE(lf@ esCVDy2fcmŊ$*Cfثf4vXBphx0K֎ls/$6avk*EUA*B> "T-$ѵIR RLjxIQCtD7i4>10e Db?}"uXHV=a[CC/ǽUeΛQ(ԂnZ':GhMFGZ2nDئFT Ód)[_r\VoEG ´}ݰEGdBn-!7'.#[qFb32U ++a L޴e*\aΧ_dCb E41?*$!5s<*Q(Ӡ6S%],DGz`v)9\V\ėÅy.Þ޳ِjud躋z,̫j#~EâX?4Tݗbϧ۹@VHt~jc]Co6 vfCQy6mr(5*/J=Зr{!G@*]R^ p_LjJG&{,\'!٫+,>"@'s0aczbR)m!~jFNs29>:ُ!…^&djE2Y4/;4݌\ :t^Vb*>;Tqo"-zmM.X3agAQwVxNFu@NyF""a)Y7"{ܔ$RR'Ɉb1"LQB!<5nQ(zbXWS{0#o0*GGG5_[Zk Y" 5HIF y Ahҋ} cek} $n"$[jPg,XSR!kV"rگ; Մ<#웧\7d6UMe~". @ul*ez*׫eܢ 2t@i({H F# b&G@4<uP 22Eǡ5ոG'WQO`W46ƿ]j92#eG #JXʫQCb;?zR fByYu Eŧ僧TxٴM&mdyX?L{%f- :|PfiX= t,kmuPT#Yv#& Y;EF}E#}fH0VlIwê?%9_8,gK(51E7w=u822![N^ШgnWzudl.~!g9-vޱy?:EK2 :8 '74+< Ei0`'uJOo%l,~' cF掄hj4so(Ԅ~ˬvWwr r*mypA l_z4|268dz޻I-Z1Ez  jxZruR39J`f4suW>9ӗٰ$UuDu/G ?eNvӹDTr%!o?yiy`hU4MeDsgG(ϵ-}e3fG)X1O e3:jǰ:U4UVnu뼡kj?E~9WͽJ@BxX V5ijUsOѭ Zܺؽ5XJ-0߇ D(p8}2q+Tcs)u^"S"15x? p \(IGoGQڏ22Wgɔq(yiOVO$&Z'H, T@VXjȵ5FhšH̒()v#q/$%w fDfE璐Fo~c =.T>s".qpWg9:<j7&;/\P>ȡUVN֢E@{8`܊%mV®G&^]䣨F6% Ev|%g\0"nP[PZfp'J=Rǝϝɔ~WP3&?za+߂Tf/>u&*uUqy?t΋52^G𪈼ĺBe!ix>nz!R1,b_XfGmE.'P5xTx r6YmGtv鋝{2-BYF;cњpFEԥ\P 5Ș/K^ޛgKGtZ{[+"}xlOxЩч5 )ף #.hTk>z)4R&p)6ς׮_ke1vD%FhTsSvlf|rSTˉ˺c %Of<-e3#Ԉoŀ`1籴ث>RZ fIb9& CQ Xr?Lua:8,DoQRskaʗO6{',\c9Z!s^# /OWClQ߽&q)$kpH QHhκxApR$i rw$*D-rw) D9p4 ӕ!1yv(H( 1_߆ |`wT脙K7J-h zj40Yi{+F 3I(K &:))&BP7Xt?} YXN5Ϊw'(t.f8yQ,tfnN=I?<1z/ jA >YM,zNfV=3"d[G{ BH i~rvy}wB>| Pzb QvDt^yk(%f"?-/Γ + y"pZ4e\.û~)ܑItJ1k$o~M^8=M!j 79JΗeAHU$>EfZAVpB33$[Ԭ>1uX}r.l[U*Y,CSR ?U,ĭ,]_]s͊?c FBm2.8>C A|R ->"JkTW!\ [_ [#!d\gVad0YAqpqZdz4k<@25L!!,|^EfN7Md}Qi-[U+~W>+'d*RɫzaQjs~[{vI6}"3hfFQ<ﳢ ?5C|2c\ !GA+֩'Jw~q+ffi3Hc I;'I $CɃ}0E%B1U >iST%hn\'G(U&%nJ8V5Tyjݏ D=xTg\ 2̫uIca |2.!inesᩍI@o3%S"uUx6]mDcuzv*(HhFt%=9kXCAʁʁʁA;uU26?oWF)Bxn"_WvbPI3PH,107P "~)4<"MŗЍ`qecI Q$5UZ^682d3{ !һ|inX y|;E*.zI_1*Wޓ3T'1m%5XR.}y%Yp bAh>*tɗɄm{N`B Ku3ۛ8ʢM&?W>耉΃zZ>t^ކ>_-n{qa{/H*h,&"gAY"U1gɼ7^u Y]AX|k5_Qk|Ouy>1ת)rJ\@mxq DTXC̎g5A-h.sG>1ݼ;d Q!SϨmY"+)d 宧 uMJd=3,L\9jn`V=%MYأmdxQ76>%֖K nGt3iR{Z,/ yQ='i h} fTj [3_>n/ cK&J;Kmݯᙓ?.܉ 5T1{3=q%E&>Ey)wuԬ.cʢ#[7u #~uܫ&nf~1ȏ,T|DB[]2:U ٸ 2YXmu82bzrg_hu.>,}7[_"!$t,;l2]4w"TnpZ?@:#2 \(803PvLlr!E5<ʪV8B1n l =vE*Azr=C"֓[l#hq6|C]v>*E걕5xv 2ۙŹ}uA *?R+vbp?*GUkNӢTǬ#ct# D^8ZiL_lC8ѰN ubգGybjTpF>>g<0)ZĿ k6⚑$ ,g:aʗ/ @umX0lmfcPK 5!zvXơV=#2O!,x(Z8ZU_HncmbO1M٢%xM[O6FpCB\pEܝә~_KДUvYajC]L&%w)p۰*+Mq΄sMW h-X:<vL4x"O?v/B;B1YW֦6iSo_n"M$hgSH45צ92(2"X͔7De#PMBp}SJ fam%+<T2:B }c VEќ5w5RrRՋɊ-Pr@,jbT2&b9ȊMb0ݛ1.nK4j`NM{Ѷ"i+#pUf}^L5{}mDi0َ[W@@s^+ LNa!B20lZUG0Ge *aoS%&ٌdɕqBp@N t]фnMVɜfu@OqV*y&2pO$tOm<Act{ScjGCQ+ j5N_x͗qm3Ѵk-{cX?Kg%8&N4* S\Qb2XPcCk|Pk-g畫Qu~H@,; m>yB6 !C2U&ܽ=k&'K|{qE h=eVƣ HV\+#Q fuKKOT(UpE~tW0LHwVטl4Sעt͌Bѓ2.b] 0(H9'2'A H+U>H=aga럔01c=Vu2BO>BQjyh5T kzED El7-t󶤫tZ`yF;'$޸^[|K]S4C ,9}(Y9E1k{Śl,mTO@M[i3ʍK ^>jY F*rF ۼ4kX[$\F+jwe޽2`Gӹp͐QI(\eRnSTBqiHD456!wT8 C`A""z!ᲡoVjSS&BĴT;k}c9O%J+hHmUmyhTas#W >,_z;$tP |;r>y(6~]Mo}`Ck#][d b&GGk PJ_sņ]4G>8rhϕkmtXT0g޽V$]-hebc|u)ui@(a`!iفZDYzNjj ?I VFr&*Ahe9V^!,,3:޹{s,J@0Lu͕ &R7C^Kڍd8ڍ~k7rj spH'UQ\X&V6m&bBV_ BB'̾=+O}B.J9COE\5|-DUFɮ(o4$ A= ?A-̴AV/:/OC1EȿL1hǰ),Ū;~ϗtx^+ 5?1PH`Քh8ڣ&s-έknyFc|"JB4 %G> Yibc9ʷDso3K>[;?}%bkV+RxAT?jBP~:(WU݂K]k6vN"oRBAe[ NiV+K+p\k﹑5f&^lae,=&yG+\Bceo&m :o3S85ݨcEgFSU9-׏5bι{5ҙn^fڪH'ACWI Cf]~(ʪ ;d|<3Uӿ,3N,}ǼNn+VRc^“i0;v4fCeWgxHOOw'ܮp2p9;qyn*f+5(P$l;ΔCV$b;ly~=eIgX cK7Fcó)&Ƀ0-´0iYHw昈&W $9sUK}\chPHCUj93YFn'~2詆o#{2<3KC_KGP\ op`ܝ֜N%,lg=(̠'/s9lD!/t&My.~MWr>\vfyFQLiU)^MZ]3?BQQ8tHqT0 j6;t9<[>9Clb#E>Z@ҝos/.oe=o}M5D%YoС|;t*Kg05o6#}DG丂7󈞑N؉ 1=6Cz8CqAK§OWQ#)rSk?zMZ?Xt9; cQ3M3SiK?堂oDOhyh{^ qͳՅPK|j(kE&4(ȥ2meK1˵.}ߌ01;(p}W1?-+N@I㤽Ph]Ab 0 xowt-L9޺X-HA 8hIP"&$sjT Qs*D? ܻ&x\ڙϡ.>A\tzl ,eWGB.z"e$^9dEXM[iCE)2%. kpKSJt'\?71n`ߥ#JϜ_،,Ȫ.2X5*BOg #S +`ǫoUz5קOn 4 +j)%\a8r* y1(ͦ%yJ&HaA " `i ԘPy$iqp^"D:mاsۘ]#q)ͲSy~4w3OI q/\CͲ)zÐ+e*b^rsK@V@چ8+9?G {-/!\~甤i> JJ$KE+s׍ańмS֏dIZ:_7{cb)G^X gY} @Sd;E [ yVѝޝT&I&c4u[r7:y_bP<1:(e`ㄝO y Q&?THO7E2_Ff{ϯAbR'9$'%Bj! a1!_\F=X7 {dt d!_p$`%P-ޟeYFA]fTbL iim| q,[ބK-ƿ-xRV cȰvf{T[ K߭{8XN6u;_# \%YQLHU~2c]c3[J\:NuN"R17 W,4'1ƙs0lL-F me'dƃvg6,ЉLB'4.-9ۚS:V0_3vϮ )/ @V#礉Ob¡HXП$v*4jyCJ;੤\ЭsH}Y Τ,)vq7n:|B=E&QԢ̊*Ȑ( 8d9Q'8J5=p-zCC76[[&nKR?'J*4lx^i L\WԓK\懣v)3J3D}J՗t93j c;ZvME;=Ű^_wjdh4Tm9D `P Giݘ`1|o:/I[Bhe< c[{D$Q=iDg\[ek=bfҪ~t=?F=BFZ)E*W Ǹ71@%4fntIbqL_<oZkv~@,CyϘwUox!=qn]x'U&tPuk$g6 Xה8)'JExLߌ !RjW:6E0z.+)φ\ROpݴG}Ac⾘nBj q`NԯJ;?Y1cʱ㓹Gkm~te)ᠭ>&YiTZo1+A7Ź:oZ aT `;P8)[m?!Y@T1H/[rI{ v "T"rh° \㝂!7x곰o3iS^!4t#l~e޻bQOA~?H"ĤHNk05 cBogn{UMɑPωXW\Rn|ic\0+w}W-"J, ՞Loe<".wѳX Q9_),BS}dV|S{5̛Y#Wh~]\?\M8@ck}܊m-CE1=FdZ%KFI!2~MSrp:]vw3ҷs/k8s4invh#RnPGh.]ṯ轚-JUm 2zHoƎQA fWvUcZM- ƌѠji)7(9kXe.N6ZIrs.s;'6L$rOB&?.I/+c;BG%oOY4|x侘O;Oۇ\ﳭt4Qen+Ul͙#`$ 85aV.M1uiW뭪:<ډo*m5dYi&EĽf1 ]J 4R`ֱ֚keV7 +^J|,qy[GGlAhVD_=~tɤ~ q ;SQՎ3Urȣ6yW#@vC27v&Q]P;tngNK#rV^H4bYC1{hx-;Q9jHj3 02Sxz.7J:X7nȆE #ˇB '/􉫇 1JOʹlI+/:ȷtg?&[GbH(LLga_/9 }!b0R>u\I(.@\4uSEL-(wlby#_Uv"!(flT'}8\.>ppC2nU_*_>L&q)3"Zsf i T}Enr㬫DYu}53 ݐKOU{?kq6/~Γ&RwצO:fLn&<ިM6A2x}-f^/K.Ax}oU81dٮ2jk1oڱݑ~![z꼊,ܷ pCgz3-Bmy,Ұr1)}0,{j0JU蒖~lVPw]FBNҨ=cf2zGb5_hF9pInLGAؑB}I.]RylGͿs286XojojojojojojojojojojojojojojojojB-'MϽ5 ^gs [XۯBvW۟ئ:AoeQTT<Y[HO[kDMΆV%FYQܗŒt V=N(N ٬ɢMIq?f|7IQO$(jO4uKL^40A@cA4{Ycj3Z)PWz脦%xsz^dv5 Yd=.*ڤpqf.LӞ8r2Sr(e/{cc ThIt.hjÚ,9,ygp . ]5`Yݕw)f$O;^i=򗻁p=X=KtOukR0VmEJ&*)+ިNj˳0=;zN-2WԛOg]-ѫ>D,;37t}t*[X/VRz/y~}.^ [\v=ѩ9aNf,[Ŵ eQTV _bbtDnB:0=bՎ!8cC.-% zi;n!jV|s[Rm| 꽱1(pJH{ŲGH7z5u"x;[ |O]9f3^ߥ ߼3BN^jr,y.\e?v* 20}x@f/- G`mTߴsc0}Gkek$oI1˩ͧDЃ1d-4w G22xR EE" ϛ1eAAc ^=6{ 4ݏt0Bt:m{R ùE׈@1?6ōh@`j)DM2xnyp$:< z\^n!DGx/4ҀсN7DtWdLDF~z?mDv8>; ߃x=l.̄^̄{rSU//]/&X.l=^NF6wOt/LQ{G.os }yp49hqR7C65rNZٛ-I41<0lLtSc\ԳtZ G>W ^b?9n!gwB|߱=4E |(E"1ֻ1ފ/Q;wƗ-FM6}3sw{ 'HE8|ouNF1c2Cq:S) C ?;|&GJ~h5|>i f|prz%:f]gZ|j@*;k?yDY=GИ^7FXͿx-vxO Tv!DZC"^TtA_w¬|$JAA$$(#ANTP|;vyE.F"xOR̔A{3ۥ*K >o'7l z>V< ح{ *}v32[zHmm1z;|h܅8n^ޫ[!,tukr J2 xk ;%{8CV _]ϱƂ~N/oO/GW}{ǀnm{׃CwuĊYppPQ}~=: n AAG2o#Z/ oƂou| Od+G4˕OW=g9G7cLYc2R h8`}Lc$AH^_ɰIDB@aQV`/ ӼY“,!>WH4&D/b^ij1 d3rYqSu\2l=؝D.z0'cóvqY$(֦SzB*j7{"X5Q{ Ngڬ̾޾%dF%rL) ~v0PutG,_nhnixAC)SU7ccd^^NW7;GXk쒲DoNsNiԈɭΩ #Zo"ԯ#))]02u8?eaj)dl5o-X"[j~8:ݫվpZ›ٱ?d~1Og. <>_^F!Y&=iP3 ׋̂ۜ# sSp00fqBAA,Cl=6-6NtbCRZ w3*N@ $$Yʠhnaihadcf~R@RW[9 mX|@S'+_#i8,,n ۿ9P98;ܬY-3?6 1SY`lcnOHDmnjqvIfFh'IӿKqtu#ș?)Kh+Ija'nicoW77 3_L\{Ss?g pwB/pMuqy%iu ?R:#rpmlint-2.2.0+ds1/test/source/no-spec-file-0-0.src.rpm000066400000000000000000001040431415540642600222340ustar00rootroot00000000000000no-spec-file-error-0-0> )lp c92b73410c61d1fefc50d63242bfedd73f66435e97728c35af09c24417b215c053e208380070165466e16101807f41dfec47d835v͓CKox\>-?d  #HLdh u    b dlt|(FGHIR\ ]^6d7e<fADHNCno-spec-file-error00None hereno-spec-file inside rpm metadata.^ localhost.localdomainGPL-2.0-onlyUndefinedSource0.tar.gzhttp://rpmlint.zarb.org/#no-spec-file-errorlinuxx86_643T^01^ b940cfe91abf3d568a27fbe902ea1c8b46ace342c766d3d981a5e74b228162ff3b9253701752465d5aba9edfd4729d5507e7e1a9a2e66faa5d10a8187c1e1af8 devdevdevdev  rpmlib(CompressedFileNames)rpmlib(FileDigests)3.0.4-14.6.0-14.15.1localhost.localdomain 1593183213Source0.tar.gzno-spec-file-errorcpiogzip9utf-86b81dc1e52682272a096a727b6d32de3cf147df4c1a8c1647a21ec18861b0079?0C.LХ۶m۶u۶m۶m۶mܶm?1Y̳ɬ8y*##rQlČ12a7g3&W#g / @xm69߫ g_F—lBUӖ6_U`e*W&vlgEqP,q%\2ĮdߗQru;a}:kwn(>L Ixk$H1e>o xև&LKs!POaY5'M4CAQ g !L!k׽={,I8>5IN]=8+)V ~YS-zѳ3]E&~Lx;]H5yv/-??o/֯!EbaoϭoǯCS/a]\Ŧ2욅D αo`Xuy7ϝ[p!2 |:ϫs~UC}tD\kV_u؃w/a_ع_mlgT¢SnobDħ{`߂_ +q}:8ȿ}eGvE c*O _; #N~ove??h]HRo$d~R tCļD< ,ys:| w~ %o]l aA>=me 8 gnҎ: Iڥx6WDNS!,QF9 -EUaAJ(X!V MW95-M 0'Q4>ūt2ڛA' :o!:"5a-ߢAdDaA}IZiSHP%H#X%XZ4 ,kUn'5zjY Liw(dF*-X5Av rkI0Q(o;%24&бA1q)Ts-*0WRq8˅DޫqLh 4(0aDul!~}@A%Τwa< QՋYR)69["p/(&y?:(L@.5{ 7B|vڔT#oAt" M_梀: ꫵXJp `BX|aa;4a f Jp|NǬ #X9 ,zh" n%М|Z[CRsmM (K^T"a4{/:/_5JUe Wu@E0|̩@X^^XL_؛qwˋyZ_z?!hNÁY9@)*@"yftvgCW}ZmbcCJ ^N?YBA5_Z&ܟT>LzG {V 2&Q&u $"p5)C ^2^ΣYEMgGli:nCBvKDɗ+S/ϣC$ċx@ G .pRka3 X&^SCHp J0 2O'jւL)$rӁ,[@,qkN2h毃?  Gw#BL#ql(@~+x2}{ғx kx7[߃;!s^d#+*lCh]On/yDYD츜cСN?YAXemkIdLQu=*||][[svySa.˻I #pDž(k %hx}F{pc_T\p4mnHy8DV=UH(GT+Uo8j;w`ooDٱ5=JwhǘJ! ^Zb0z X!Tn:\ r/ļ!iFz[3Cģtx-QQ L]c]Uzx Wx Xރ!530nXG lHؐ =ífN.|},2 PWu>WOW)w-81 L :,Qx~B*> Ս۫67VNPNlЈq/AAMC|͞zUz+Պ*f-UtU/ ܿKh1뎮0)>!ظg c}a2`50gyUf0HekcHY?-Ag6~IxROZwδ_H:~+`FCqE:,O7Үy:;2f(̬KL!su52l kxxt;;oK=Q(;⠌*1"]3P,LOD_="f;FHrZ)^!-mk0-LJaN7RKxeLo՚~r6B+.ξ ~KAQӡF~vRg5t2q뺄oQmr`+fQd@ujN? ;U1teC)'i%1$K T}nܾ ջbM$7B4'2kHeLv"O.?F2}#o -2\T^!7Q#2 f3R]))H|ok1龶]f -nVye/%njZ.yzH7Ŕ]&PSdSI&O|EӺH E[/ɇ/)`jSq߳@L+ [l+hLmpNE@`]ATڋ0ڸک8ZZa/EbT"PDb/51t7x"AjʓN[gGXx{ "%[]Z{O=IӞzU騅YAf:34jFѡO[-[x[/[_E$N҈ 㠳|-{'kChC50nU=`4'7(`rfȎsHI |Uu@jeAdrw>5=">#@b3L{;ԦU QrVBҎGpC6J4MĦ&6h@cSBﰢceY4DpjS ݦJJ2kS~,Z'UIVm1tp}vJsuj ?Ѱ\S]+KCfWM͹:|A>$"q?ag .% A((=Hj` Tra[D7RF0אNY@yHCC\H jG$H̟J;;Sq{&3 yЯ6%ׁ-7y@p~`5y q4XxNك Kmg䥦ԣ \m>!Lº{%;:Q9!lH 4c,cZ~[!UK|K%%#򍬌KVDnU? 1P^_%N1ҽhW.7{s&$;+}S`geu.TeU5ɲ'*|heR{G-HE(lf@ esCVDy2fcmŊ$*Cfثf4vXBphx0K֎ls/$6avk*EUA*B> "T-$ѵIR RLjxIQCtD7i4>10e Db?}"uXHV=a[CC/ǽUeΛQ(ԂnZ':GhMFGZ2nDئFT Ód)[_r\VoEG ´}ݰEGdBn-!7'.#[qFb32U ++a L޴e*\aΧ_dCb E41?*$!5s<*Q(Ӡ6S%],DGz`v)9\V\ėÅy.Þ޳ِjud躋z,̫j#~EâX?4Tݗbϧ۹@VHt~jc]Co6 vfCQy6mr(5*/J=Зr{!G@*]R^ p_LjJG&{,\'!٫+,>"@'s0aczbR)m!~jFNs29>:ُ!…^&djE2Y4/;4݌\ :t^Vb*>;Tqo"-zmM.X3agAQwVxNFu@NyF""a)Y7"{ܔ$RR'Ɉb1"LQB!<5nQ(zbXWS{0#o0*GGG5_[Zk Y" 5HIF y Ahҋ} cek} $n"$[jPg,XSR!kV"rگ; Մ<#웧\7d6UMe~". @ul*ez*׫eܢ 2t@i({H F# b&G@4<uP 22Eǡ5ոG'WQO`W46ƿ]j92#eG #JXʫQCb;?zR fByYu Eŧ僧TxٴM&mdyX?L{%f- :|PfiX= t,kmuPT#Yv#& Y;EF}E#}fH0VlIwê?%9_8,gK(51E7w=u822![N^ШgnWzudl.~!g9-vޱy?:EK2 :8 '74+< Ei0`'uJOo%l,~' cF掄hj4so(Ԅ~ˬvWwr r*mypA l_z4|268dz޻I-Z1Ez  jxZruR39J`f4suW>9ӗٰ$UuDu/G ?eNvӹDTr%!o?yiy`hU4MeDsgG(ϵ-}e3fG)X1O e3:jǰ:U4UVnu뼡kj?E~9WͽJ@BxX V5ijUsOѭ Zܺؽ5XJ-0߇ D(p8}2q+Tcs)u^"S"15x? p \(IGoGQڏ22Wgɔq(yiOVO$&Z'H, T@VXjȵ5FhšH̒()v#q/$%w fDfE璐Fo~c =.T>s".qpWg9:<j7&;/\P>ȡUVN֢E@{8`܊%mV®G&^]䣨F6% Ev|%g\0"nP[PZfp'J=Rǝϝɔ~WP3&?za+߂Tf/>u&*uUqy?t΋52^G𪈼ĺBe!ix>nz!R1,b_XfGmE.'P5xTx r6YmGtv鋝{2-BYF;cњpFEԥ\P 5Ș/K^ޛgKGtZ{[+"}xlOxЩч5 )ף #.hTk>z)4R&p)6ς׮_ke1vD%FhTsSvlf|rSTˉ˺c %Of<-e3#Ԉoŀ`1籴ث>RZ fIb9& CQ Xr?Lua:8,DoQRskaʗO6{',\c9Z!s^# /OWClQ߽&q)$kpH QHhκxApR$i rw$*D-rw) D9p4 ӕ!1yv(H( 1_߆ |`wT脙K7J-h zj40Yi{+F 3I(K &:))&BP7Xt?} YXN5Ϊw'(t.f8yQ,tfnN=I?<1z/ jA >YM,zNfV=3"d[G{ BH i~rvy}wB>| Pzb QvDt^yk(%f"?-/Γ + y"pZ4e\.û~)ܑItJ1k$o~M^8=M!j 79JΗeAHU$>EfZAVpB33$[Ԭ>1uX}r.l[U*Y,CSR ?U,ĭ,]_]s͊?c FBm2.8>C A|R ->"JkTW!\ [_ [#!d\gVad0YAqpqZdz4k<@25L!!,|^EfN7Md}Qi-[U+~W>+'d*RɫzaQjs~[{vI6}"3hfFQ<ﳢ ?5C|2c\ !GA+֩'Jw~q+ffi3Hc I;'I $CɃ}0E%B1U >iST%hn\'G(U&%nJ8V5Tyjݏ D=xTg\ 2̫uIca |2.!inesᩍI@o3%S"uUx6]mDcuzv*(HhFt%=9kXCAʁʁʁA;uU26?oWF)Bxn"_WvbPI3PH,107P "~)4<"MŗЍ`qecI Q$5UZ^682d3{ !һ|inX y|;E*.zI_1*Wޓ3T'1m%5XR.}y%Yp bAh>*tɗɄm{N`B Ku3ۛ8ʢM&?W>耉΃zZ>t^ކ>_-n{qa{/H*h,&"gAY"U1gɼ7^u Y]AX|k5_Qk|Ouy>1ת)rJ\@mxq DTXC̎g5A-h.sG>1ݼ;d Q!SϨmY"+)d 宧 uMJd=3,L\9jn`V=%MYأmdxQ76>%֖K nGt3iR{Z,/ yQ='i h} fTj [3_>n/ cK&J;Kmݯᙓ?.܉ 5T1{3=q%E&>Ey)wuԬ.cʢ#[7u #~uܫ&nf~1ȏ,T|DB[]2:U ٸ 2YXmu82bzrg_hu.>,}7[_"!$t,;l2]4w"TnpZ?@:#2 \(803PvLlr!E5<ʪV8B1n l =vE*Azr=C"֓[l#hq6|C]v>*E걕5xv 2ۙŹ}uA *?R+vbp?*GUkNӢTǬ#ct# D^8ZiL_lC8ѰN ubգGybjTpF>>g<0)ZĿ k6⚑$ ,g:aʗ/ @umX0lmfcPK 5!zvXơV=#2O!,x(Z8ZU_HncmbO1M٢%xM[O6FpCB\pEܝә~_KДUvYajC]L&%w)p۰*+Mq΄sMW h-X:<vL4x"O?v/B;B1YW֦6iSo_n"M$hgSH45צ92(2"X͔7De#PMBp}SJ fam%+<T2:B }c VEќ5w5RrRՋɊ-Pr@,jbT2&b9ȊMb0ݛ1.nK4j`NM{Ѷ"i+#pUf}^L5{}mDi0َ[W@@s^+ LNa!B20lZUG0Ge *aoS%&ٌdɕqBp@N t]фnMVɜfu@OqV*y&2pO$tOm<Act{ScjGCQ+ j5N_x͗qm3Ѵk-{cX?Kg%8&N4* S\Qb2XPcCk|Pk-g畫Qu~H@,; m>yB6 !C2U&ܽ=k&'K|{qE h=eVƣ HV\+#Q fuKKOT(UpE~tW0LHwVטl4Sעt͌Bѓ2.b] 0(H9'2'A H+U>H=aga럔01c=Vu2BO>BQjyh5T kzED El7-t󶤫tZ`yF;'$޸^[|K]S4C ,9}(Y9E1k{Śl,mTO@M[i3ʍK ^>jY F*rF ۼ4kX[$\F+jwe޽2`Gӹp͐QI(\eRnSTBqiHD456!wT8 C`A""z!ᲡoVjSS&BĴT;k}c9O%J+hHmUmyhTas#W >,_z;$tP |;r>y(6~]Mo}`Ck#][d b&GGk PJ_sņ]4G>8rhϕkmtXT0g޽V$]-hebc|u)ui@(a`!iفZDYzNjj ?I VFr&*Ahe9V^!,,3:޹{s,J@0Lu͕ &R7C^Kڍd8ڍ~k7rj spH'UQ\X&V6m&bBV_ BB'̾=+O}B.J9COE\5|-DUFɮ(o4$ A= ?A-̴AV/:/OC1EȿL1hǰ),Ū;~ϗtx^+ΠvU, wjL7u =k HV,kK6ܞ]y 6e!m޾j&f5L#?B 󵪍gOPl,Unp_:0;E΃V&ϡ> 9? PP`h8ʓ:c-֝kn#EFcl"oJ\$9[5iYbS9D3o̊s [?}%#|kV3BxϠ; ě5,sD?<-$n%ˮ +e'wynР$_+ѯ5x|6z_<=Qj3pE6PR&r㇌Oݴ|ROUٷօE[p)jn3V}J*2Gj =lL L{77u3EeUܫ$ur;A i iiYw2I KyI(b_]u'1/̠#-s>lD&-t"Nx.~NWp>_vbYDiM!V]ZC3/ \QQ8dPaD0rWճٳ{-tޤ;>lBCEj@ҽos/.oE=oCE-D)gо|;t:sg0%o6mXg东7㈮NƬ؉ 1]6Cz8CQ~ "/Q#)2S&k?9z! [?#YkAu9;cP<0N1QiK?cnE Ohyh{` p̳ԅP}i(kF6,(ӅCiW:CkM\)~Xc` 2{c{ZVi{腋Qw]jor$c!ҵ0 چ6HRXbUR޶(9͆^ dKx%~jhv_HΙ_(QR :PoGd)sF pPhg RG1FuN6ӉѰ,^wdP\<&%[}.khR%J@&)8-zQMOI%ղ\<}Mj z}:di9ё 3x&3 FMyBƉXjAT` z[٫A^tC~0M:f+73zJ GtB^ RII1?L'.ARZb 1&~G,Y s֣(M%[ &]J@jA4'R&h9ѥScoc`mW}X]j3TtTΛ~Y *)ڗiBpsCrZ/ScuT>*O=,u}2Khٙf 9z|VFXmVr򬚊<ȃqcJo  ;:qӴWϜC2ve%(3m hW N!}!](b?aǹG1 S?K8|`R׉! 'bZ;@BAw Aďq~m]?_؋ۅX;[SB#MI#[a=q'Ry1od85op%ANx\ʙp!˻Fsd~euʙ6/FZu;Yнi"i8mz:u6Dg3&LҮdS؎abݺ4CuN JWaJ=ɏwP\C,J,ş0r4)xԜ)@v:9>rthTUQd*3**wyl?:VNb5*U/`#?,S6vZ8peLh-8O8M|8l7^Cq.'D"ǻ{)&$^,+[ΏJ2d"QhkTV3⡥͵YXU INW*l]pڿLki⍧n3%.h"~@l ҫBU{Z]Ðe~C8UJ÷)^[34@8nq hSڵJlN<a>볦aN\H+A>W[$wە[x[=w+%ihr8dhU$5Wj{{'671x%yIe۾a4%L3@ؚ̢xuGngeQe_tOK[ bv$u:^'Ou] -20}>!(muEk2tc+2bXנ)y HajDUgya&3ÁrdmhS@$-muK)6-u#٨LGu{eME,KgBDl;LDFlB|䴕='J%ؘ':!P^T7Z߱TM|ogBڼ@:Ԁp "O>KSe,1(#۫J! !7,<ĖwRѱWʪ[al Ǝ܌Ֆkw3~&従Mg 2(AWpPV HCS&P~)*喇4Ϫ?Q  4]J @3I=dhEMb[$c@7-MKp_D~?=ES >LMOxɠvGʲ仁/ӌx+CB+@ !USDh'|$s|zN" *A⼋!aN,_hV@9M'bÔ5[[TQ-qq~Ue'( !v]9)ov 0Ru^U L±~*֔#C% 'e}*Ws)$]”\! (ۡkaBU|?-/B̩'HLڏ%T]S=0^ _t;gͨ5Tm9"E`9QF#c3h:-I[Bl? a[{'R>mD_Yei9bdШl?>#XH{ "Sj+uԥomFO 35;ip8/[/74 ;VR?d!}󻍪JH<ߐhH9MflHIy'T%tPt%g6 XՔ(&JGx@Mގ;#kVR;7EsZ{itfCE'En[eƱHp^NIG!4vOv'TUԝ2ktbњv׫]Y|ii0hcj!qaVlt"@[2:GZM~?N%\ehգc.zPVHsl fd-2_!r-8 $Lzh`bf9SԡX>NA΀a<[Eз)TV2rz6zk "^zC5ƨ엠z࣓%M1 ¶3K6*䈏DSs{=Օ16<4/n̕.PfL&H?Tj4zHl:d BkDdR10|J8h\2{ Y*qSォʵf8 4?#{~Djf__YdwQ $t&?L\jql[CKx-]FM/ζ7/-[BrXş{/{ p/˩vd¬ } 44')2GbٮUi;_eu h-4FRDł`Nz]zTxt)KTQ76,sߝBY 2*ļfvLp(VfxÑy3E/L h^? !D:'ŪVᮊXO>sO <p.mˡ*07m/c|nCkw:̵ c?"o[U]SeljQ>.#‚]2oϢhJۦHf !]1+\+Fb3k)R !+>1ZeZYN}zBf[Ώur['Ys#qmv.vC=6IE} aFzGS(΃ii @5T,4ke2ϨΞ f-v=aR{0lcl[wm܅0p6H!1^saKU9mx<_"=M)Vk$L?FpAՌ{ K}w!v$_pW7ǷEqn")_:*wM_V>ƨ|aQE&dȔ(Fɰ5>A4PNj̙5\ocĠuTAW#GB, —qU ~DیּrfЌ  j7P+UWƀȺŴ>Vy-.UXĩaZycn m2,4Rh¢ik^3|JІ@vo&u .RnNdG>MA9?ƭࢢW 7-SMo_dP= I^WU*jD|{AOMU ፩bJT;TN.=l`H)}9-&[Q8^=21;2BfW 9~M9Orl⃠,#2 ^Y-G,"K!.L o쨮@Tط^rJ %Mhi҆c3IP?^.XAj:!iZ*q2͓JJWTMjnjTmjgo@RpnM} cdBjrcErHL+SzxWLgQ',#&-NZ4} hh N%۳e2jHm¼t~ҿ7=ʳ/f(dNWMx{29\>zěD]¤#6?yNi!*FVO X ɟob,50xc4%"c{)b0~7@ܑF=\1pzusHx/^m ~D(8E\L!cHچųmлp`*JE0n1 ;((1`G41aPyA( P)! ֝Ԡ&p*s kʣOcl*2Sѣ욶eFViZJv짾sp(W0պZ"]{l/UՕN2{;m '<6-51S|u_&3tٕ9HkOIhU(Eᣪ,cSthZ8<P|(*8‚hG݆ě1 ٭X K W=؅^ndۍ U,'eu|ӧqr0rxږDX}fsUm RdO2d4%Iv^%mCQ`ʇ=-,p7سsDJMTcbJN޶++>%ՆYQW4 b=OggYHM(>ݢDVgrQk5,g}? LO: %w& wS?0n:qIݭ?QYwtJ7iLQ\o= 5EXxc1WB0ҕ^6:b_ڰ]ҿHBpEj Gd?d~%Ӗ31E H[ .,xrwoz0P{z/t)3amw՚w^(e%ZUP,wA)T9zC3_K[M8uwzH-6'g;v߻U "E/Z6 ,{ R -׮fG3W KHƂ$;9ŗNڭmWzR*t[ɫ_X~:1P)N'ʸ kZϩ!V-{2ѱl1 a!ꮄ6H6dlvJl eƳĂC#4OL˨VNa1!nuFd`L%eyԱ]fyg@#R6߱%";_ڝrYy 9= !zՇ|Gt▦OF5#E2hn%ošݷ e~.ϠŅl׋d*.e+66LK ]HvMf9G,CԲ<._严pm2_ٌvH2.-Bzb1|T_`d8.<9zAVTKck#SAOs%"+4{ |\JHW*{!7#+.Gn[6ΜI]<<ç7) DcрX}x4FIM[{10k;!s)ʛ{v!Ԁ(c=hČ䑚-] . dt';lLe$|h/MUn|+1t症Fh<4:PsCaϔRp4.MDYXMHv[Nѥ˩22s:RӏnU唄f#<r>t_s?Vh|= _FAdLTJp*8pNrO{zYDG?$;٧Hjjuߣ5}Ն%mz+7Rw}:cz\̋1ytąp -A<r4Ye$9d8nZ4k1hm2䉜Mݴ)մ,UszUg}=5#Hj66Q1Qٓ[T%<~Ƴc?Q|Ʌx_Q$Jbw6s^Nv'% FjkzR~Z~<nnIF N`>QO|h ||4۔yg~R ' 8șl^^wXaCW,ޜK>-AUod~J/3>oekDʥI1˩Dx=4wg22d\LH?&XC$LQHLgC8wEb3 *̂ f$~TxysY{s-"4bL XVo 0,K.X{y wK\|<8Et .€``ݓ2+$*8`tF_3ϛ?///@g/{Z^#[KSWS܃G-Ԁo+kj˟(' %<9w `*]y?28)Pgk`ǫke/_>]Asٱ#$o̸ 1a›mjžgJ?8qIw =sv58 1ͤXwM]W[w.p`Ls>f=Zoű ofAPp մ8Z%w>~(o-kD LPo$ϒc`7$8ws;>/_u܇1Ƴ @u—}:~"$PezSwc%_.fוּ/G[LSt"Fg5G/p xG|ɱ+cǠ9z;xq;T/FY|>G]Xӓ8e'(a/hk *5}(|4y0[? u]{&mgU͐S(~6Ѹ qؼ |P <ׁYo#ڷg@.^[[ߍ3S$KM@!1#=&S!sF BhYa4h[t+D%bT3KOpb򆴇\U4D^Tcy!FZV&nf\ID>s*bNrU53bw"JO/6۽2eΣhY> S'+ ]`F(48*k23zz>b0\U.^@A1uAQw?H Ǎ Jɞ|+$r9}@>Xc@$ws#tL^ϥOnuJ!_x|IYMqz&{) KD竡8<`cV^o7k '`[?B& ήCׇX޷y&I&5iB ߅/V˿VNs .^aWqyiN榌v...D `oG?B4wqvt(Us;s?y.Qrt0'2w1GP65wX/ lL,v.Ny 3s ks3 U-R+77'>ff'{;k7& %3ß]LY}.L>f殦.NnJA*I\I&7wL\̝ &vf?Qn;??"PYY3[8:qZ,-þ7MLMQWUT%##~7D ,0@e3d7496933f75ab0e2d25e289e0be612c3bab56dk?B"8~[1tL>+?d   6\`x|      4(<8D9H:kFGHI\]^def{Cnon-utf81.01.el5.centosTesting non-TF-8 contentTest package for non-TF-8 content.Vlocalhost.localdomainTGPLv2Testhttps://github.com/rpm-software-management/rpmlintlinuxx86_64TVf071e33db3bc1ee547dbb6afba9a951f mockbuildmockbuildJrpmlib(CompressedFileNames)3.0.4-14.4.2.3VVille Skytt - Test package createdlocalhost.localdomain 1454226094ZCnon-utf8.speccpiogzip9?PRn0ݳHNn] i()Xulv:MUG>ll6i{=NOq {RrF:9!咐m6 P TuV3z>b[f,WL0biy_rz0ږʂYeg܏4TIǤ¨NI*8erou< 6i{$5wMj[dUa%Ԭ2^p/wqQJݠhSVk>6һQYo͆l=& /H *k 5+ +em\UBuD;!*[8L[q~s}*E6 6L!<{_á |[|6DLN¯D£qzo W!mq58V_rs><~(Lrpmlint-2.2.0+ds1/test/source/not-compressed-multi-spec-1.0-0.src.rpm000066400000000000000000000164411415540642600251400ustar00rootroot00000000000000not-compressed-multi-spec-1.0-0> )lp ae0dc621c970f596c19516b024665186dd0bdbd69ea8f639c699ddda34082e1a9cda4cc5b7ae295503e0a41852258aad69987184 'Lfd4 4>/`?Pd  " R0Y ]l     <(MFVGhHtIR\]^def LCnot-compressed-multi-spec1.00A package with not compressed src and multispecLorem ipsum dolor sit amet, consectetuer adipiscing elit. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit.]Z42ka2home:kstreitova:rpmlint_test_pkgs / openSUSE_Tumbleweedobs://build.opensuse.org/home:kstreitovaMITSystem/Daemonsnot-compressed-multi-spec2.specnot-compressed-multi-spec.tarhttp://www.not-compressed.comlinuxx86_64p(=]Z]Y]Z1db5b9b3322354d90387b26aa52cb5205408fda3a6f040105ef87fec10db1caf47455cb6c2cf29afa0b39ee98654f90750d29b320de34d166535a94298fb852818446e8432c5d28aebc786a1a733eb99b24497e056ee0c90e931468d9330721a rootrootrootrootrootroot  gccrpmlib(CompressedFileNames)rpmlib(FileDigests)3.0.4-14.6.0-14.14.2.142ka 1566226351not-compressed-multi-spec.specnot-compressed-multi-spec.tarnot-compressed-multi-spec2.speccpiogzip9utf-8ae1fcde433042828eea898abcc75baeeb0c17faf8dbc836432482db8552818f4?O8bHmM HEӽ:δ6sC--Zĭ?Lxܗ8za2q,c󔎣OwRWe{e]X3tGp0Tif7U--CH&ws .Sk2C=e*SJ`2} [E\IEV[![{s@C4LƼҪBmYh]kT t0ls TWJpKq Qv-sm$MZss +#2 cj kJa꩔K)?*0QW >PQ5/o!JBΚm sf]jHX׶NlVw7qx|H1:+9cnSgճx@ʟmYIO&M Mm0Tz֧הxR1(F]LdzysIBy`4oLբn)Vu%r4Ò6Wp'X,§ 6hdC:>i4Fk-Y׌ p-*JQYL]B ʖQlmܢGTp!g!?p,C@qΐVW"g$m$tS1uEŀ$QSҖ =h $Ҍ`?sIHcYQ+ΠWh8~}YӻX+e}3gYܸiOuVa/c>grA}'o8G[}S;a3{,mɰeeSAY z~A҉i2JdƝ(iҁ6nH Ҋ(dqGQ4ʾ:_x߿ڿZTmǏ}Vt8@#U5;:!wLrp{Ox<x<x<x<nyҋ8xK/:;Z٧mI1Y7C/z}3=IIQӆuʓMѷonoN/o?J4rpmlint-2.2.0+ds1/test/source/wrongsrc-0-0.src.rpm000066400000000000000000000154171415540642600216250ustar00rootroot00000000000000wrongsrc-0-0t>d ,0@D e9d58aa372d01a8b00b3208bf48cf6148eb3968d XXו T>*y?id     17@H L P X  (AFGH$I,\0]8^VdWe\facCwrongsrc00Lorem ipsumLorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Yw1oldboy.suse.deGPL-2.0+Development/Tools/Buildingwrongsrc.tar.gzhttp://www.opensuse.org/linuxnoarch8݁YvYw.7fca10aefa695f704e5fa96ac50e49840aedf0bac8a80d151545e2ac50ca36e0 dmuellerdmuellersusesuse rpmlib(CompressedFileNames)3.0.4-14.13.0.1noarcholdboy.suse.de 1506637617wrongsrc.specwrongsrc.tar.gzcpiogzip9utf-8?`S]h\E͎բT8 ntIvnMaja#M*jgwv~vSE,ZYZ4b_D iZTb-G RA'A&!<;swS_,Q:pS|8,`sJ`T͏iF(YIdB`a\\[1 UEBSq" l"OV5!"6."5A&P (+9[[,ӅK/(m泝|#!GumkJv *ƪ@AVq9y{#]*BEllcM*M1jӔ"FpA!xf T@-D1^IK!k 0;dczR"",Ec]@a 4%D Cq5@N V"}H$S`H9BFb@K}*dh5AQBc@(1@MGPMRР1Ӹq@8\b*W ΀cw(FX+`m?ByD-Q&(i„KlIt 7]Tct i, CYdLt [:HZE/6ƋMI:ɬՏǒ#G6-fvj|c`g#[5{{ܾ3߳һ[4Wk45K3WٗrNzϜ;.,sܸwo݇өܨB&a͐rpmlint-2.2.0+ds1/test/spec/000077500000000000000000000000001415540642600156035ustar00rootroot00000000000000rpmlint-2.2.0+ds1/test/spec/%autopatch-not-in-prep.spec000066400000000000000000000010551415540642600226630ustar00rootroot00000000000000Name: %autopatch-not-in-prep Version: 0 Release: 0 Summary: autopatch not inside prep warning License: GPL-2.0-only Group: Undefined URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz Requires: Somethingwithsinglespace >=1.0 Conflicts: Someotherthinwithsinglespace<= 1.0 Obsoletes: %{name} <= %{version} Provides: %{name} = %{version} %description autopatch macro must be inside %prep. %autopatch %prep %build %install %files %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/spec/%autosetup-not-in-prep.spec000066400000000000000000000010771415540642600227300ustar00rootroot00000000000000# This is comment to check macro-in-comment not found. Name: autosetup-not-in-prep Version: 0 Release: 0 Summary: autosetup-not-in-prep warning. License: GPL-2.0-only Group: Undefined URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz Requires: Somethingwithdoublespace = 1.0 Conflicts: Some thing with double space == 2.0 Provides: /Something %description The specfile contains %autosetup outside the %prep. %autosetup %prep %build %install %files %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/spec/%ifarch-applied-patch.spec000066400000000000000000000011131415540642600224650ustar00rootroot00000000000000Name: %ifarch-applied-patch Version: 0 Release: 0 Summary: %ifarch-applied-patch warning License: GPL-2.0-only Group: Undefined URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz Patch1: Patch1.patch Requires(post): foo %description A patch is applied inside an %ifarch block. Patches must be applied on all architectures and may contain necessary configure and/or code patch to be effective only on a given arch. %prep %build %install %ifarch %patch1 -P 1 %endif %files %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/spec/SpecCheck.spec000066400000000000000000000024221415540642600203070ustar00rootroot00000000000000Name: SpecCheck Version: 0 Release: 0 Summary: None here Group: Undefined License: GPLv2 URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz Patch: Patch.patch Patch1: Patch1.patch Patch2: Patch2.patch Patch3: Patch3.patch Patch4: Patch4.patch Patch5: Patch5.patch Patch6: Patch6.patch Patch7: Patch7.patch BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) Provides: unversioned-provides, versioned-provides = 1.0 Obsoletes: versioned-obsoletes < 2.0 Obsoletes: unversioned-obsoletes Obsoletes: /usr/bin/unversioned-but-filename Provides: /sbin/another-unversioned-but-filename %description SpecCheck test. %package noarch-sub Summary: Noarch subpackage Group: Undefined BuildArch: noarch %description noarch-sub Noarch subpackage test. %prep %setup %patch1 %patch %patch -P 2 -P 4 sed -e s/foo/bar/ %{PATCH5} | %{__patch} -p1 %{__patch} -p2 < %{PATCH6} patch -i %{PATCH7} %build # %configure # %%% %install rm -rf $RPM_BUILD_ROOT %clean rm -rf $RPM_BUILD_ROOT %files %defattr(-,root,root,-) %{_libdir}/foo %files noarch-sub %defattr(-,root,root,-) %changelog rpmlint-2.2.0+ds1/test/spec/SpecCheck2.spec000066400000000000000000000021641415540642600203740ustar00rootroot00000000000000Name: SpecCheck2 Version: 0 Release: 0 Summary: None here Group: Undefined License: GPLv2 URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz Patch: Patch.patch Patch1: Patch1.patch Patch2: Patch2.patch Patch3: Patch3.patch Patch4: Patch4.patch Patch5: Patch5.patch BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildRequires: source-for-second-rpm BuildArch: noarch ExclusiveArch: i586 Requires: Oneanotherthing>=1.0 Conflicts: Onelastthing==2.0 %description macro-in-%changelog-deptoken:- (Developer Note) Macro can cause a warning which you can escape by using %%buildroot or %+buildroot or %.buildroot or any othersign prefixed with % for example %(-, +, .) and so on. Make sure you exclude %_buildroot or usage of % followed by _ %prep %autosetup %build %configure ./configure --libdir=%{_libdir} make %{_libdir} %install rm -rf $RPM_BUILD_ROOT %clean rm -rf $RPM_BUILD_ROOT %files %defattr(-,root,root,-) %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/spec/SpecCheck3.spec000066400000000000000000000012411415540642600203700ustar00rootroot00000000000000Name: SpecCheck3 Version: 0 Release: 0 Summary: None here Group: Undefined License: GPLv2 URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz Patch: Patch.patch Patch1: Patch1.patch Patch2: Patch2.patch Patch3: Patch3.patch Patch4: Patch4.patch Patch5: Patch5.patch BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) %description SpecCheck test 2. %prep %autosetup -N %autopatch %build %install rm -rf $RPM_BUILD_ROOT %clean rm -rf $RPM_BUILD_ROOT %files %defattr(-,root,root,-) %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/spec/SpecCheck4.spec000066400000000000000000000011051415540642600203700ustar00rootroot00000000000000Name: SpecCheck4 Version: 0.0.1 Release: 0 Summary: None here Group: Undefined License: GPLv2 URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz Requires: require Provides: provide Obsoletes: obsolete Conflicts: conflict %description %prep %setup -q %build %configure %make_build %install %make_install %post %postun %files %license COPYING %doc ChangeLog README %changelog * Wed Oct 23 14:15:39 UTC 2019 - Frank Schreiner - changelog entry ....  rpmlint-2.2.0+ds1/test/spec/SpecCheckTemp.spec000066400000000000000000000054461415540642600211460ustar00rootroot00000000000000# 1. rpm-buildroot-usage: # %{buildroot} should not be touched during %build or %prep stage, # as it may break short circuit builds. # Developer Note:- This file contains %buildroot under %build macro # since rpm-buildroot-usage.spec contains the # %build under %prep macro and we need to test "a warning if # %{buildroot} is placed under %build". # # 2. make-check-outside-check-section: # Make check or other automated regression test should be run # in %check, as they can be disabled with a rpm macro for short # circuiting purposes. # Developer Note:- This file contains `make check` inside %check # %description %package %changelog to test the required check # not in out. # # 3. setup-not-quiet: # Use the -q option to the %setup macro to avoid useless # build output from unpacking the sources. # Developer Note:- This file contains the %setup -q macro to test # the required check not in out. # # 4. setup-not-in-prep: # The %setup macro should only be used within the %prep # section because it may not expand to anything outside # of it and can break the build in unpredictable. # Developer Note:- This file contains %setup -q inside %prep # macro to test if check setup-not-in-prep is not in out. # # 5. %autopatch-not-in-prep: # Developer Note:- This file contains %autopatch inside the %prep macro. # # 6. %autosetup-not-in-prep: # Developer Note:- This file contains %autosetup inside the %prep macro. # # 7. comparision-operator-in-deptoken: # This dependency token contains a comparison operator (<, > or =). # This is usually not intended and may be caused by missing # whitespace between the token's name, the comparison operator and # the version strig. # Developer Note:- This file contains < and <= operators as seen in # Requires and Conflicts respectively and is responsible for # respective check since it does not contains spaces around the operators. %define __find_provides %define _use_internal_dependency_generator 0 Name: SpecCheckTemp Version: 0 Release: 0 Summary: rpm-buildroot-usage, make-check-outside-check, setup-not-quite, setup-not-in-prep, %autopatch-not-in-prep, %autosetup-not-in-prep warning, comparision-operator-in-deptoken. License: GPL-2.0-only Group: Undefined URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz Requires: Someotherthing<1.0 Conflicts: Someotherthing<=2.0 Obsoletes: /something %description make check egrep something %build %{buildroot} %prep %setup -q %autopatch %autosetup %check make check %package make check grep something grep -F Someotherthing grep -E something %install %files %{_libdir}/foo fgrep -F something %changelog make check egrep -E something rpmlint-2.2.0+ds1/test/spec/buildarch-instead-of-exclusivearch-tag.spec000066400000000000000000000012411415540642600260550ustar00rootroot00000000000000Name: buildarch-instead-of-exclusivearch-tag Version: 0 Release: 0 Summary: buildarch-instead-of-exclusivearch-tag warning Group: Undefined License: GPLv2 URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz BuildArch: x86_64 BuildArchitectures: i586 BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) %description Use ExclusiveArch instead of BuildArch (or BuildArchitectures) to restrict build on some specific architectures. Only use BuildArch with noarch %prep %autosetup %build %install %clean %files %defattr(-,root,root,-) %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/spec/buildprereq-use.spec000066400000000000000000000007621415540642600215740ustar00rootroot00000000000000Name: buildprereq-use Version: 0 Release: 0 Summary: buildprereq-use warning License: GPL-2.0-only Group: Undefined URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz BuildPreReq: Something %description The use of BuildPreReq is deprecated, build dependencies are always required before a package can be built. Use plain BuildRequires instead. %prep %autosetup %build %install %files %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/spec/comparison-operator-in-deptoken.spec000066400000000000000000000012301415540642600246710ustar00rootroot00000000000000Name: comparison-operator-in-deptoken Version: 0 Release: 0 Summary: comparison-operator-in-deptoken warning. License: GPL-2.0-only Group: Undefined URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz BuildRequires: something>2.0 Requires: Something>1.0 Conflicts: Something=2.0 %description This dependency token contains a comparison operator (<, > or =). This is usually not intended and may be caused by missing whitespace between the token's name, the comparison operator and the version string. %prep %autosetup %build %install %files %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/spec/configure-without-libdir-spec.spec000066400000000000000000000010111415540642600243250ustar00rootroot00000000000000Name: configure-without-libdir-spec Version: 0 Release: 0 Summary: configure-without-libdir-spec warning License: GPL-2.0-only Group: Undefined URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz %description A configure script is run without specifying the libdir. configure options must be augmented with something like --libdir=%{_libdir} whenever the script supports it. %prep %autosetup %build ./configure %install %files %changelog rpmlint-2.2.0+ds1/test/spec/deprecated-grep.spec000066400000000000000000000007131415540642600215130ustar00rootroot00000000000000Name: deprecated-grep Version: 0 Release: 0 Summary: deprecated-grep warning License: GPL-2.0-only Group: Undefined URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz %description Direct use of grep as egrep or fgrep is deprecated in GNU grep and historical in POSIX, use grep -E and grep -F instead. %prep egrep something %build %install %files %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/spec/depscript-without-disabling-depgen.spec000066400000000000000000000012131415540642600253440ustar00rootroot00000000000000%define __find_provides Name: depscript-without-disabling-depgen Version: 0 Summary: depscript-without-disabling-depgen warning Group: Undefined URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz %description In some common rpm configurations/versions, defining __find_provides and/or __find_requires has no effect if rpm's internal dependency generator has not been disabled for the build. %define _use_internal_dependency_generator to 0 to disable it in the specfile, or don't define __find_provides/requires. %prep %autosetup %build %install %files %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/spec/forbidden-controlchar-found.spec000066400000000000000000000014111415540642600240350ustar00rootroot00000000000000Name: SpecCheck_forbidden-controlchar-found Version: 1.0 Release: 0 Summary: forbidden-controlchar-found warning License: MIT URL: https://www.example.com Source: Source.tar.gz Requires: something_needed > 1.0 Provides: something_new Obsoletes: something_old Conflicts: something_bad BuildRequires: gcc %description This package contains tags which contain forbidden control characters. These are all ASCII characters with a decimal value below 32, except TAB(9), LF(10) and CR(13) %prep %setup -q %build %configure %make_build %install %make_install %post %postun %files %license COPYING %doc ChangeLog README %changelog - This is a changelog entry with forbidden control character rpmlint-2.2.0+ds1/test/spec/hardcoded-library-path.spec000066400000000000000000000011451415540642600227710ustar00rootroot00000000000000Name: hardcoded-library-path Version: 0 Release: 0 Summary: hardcoded-library-path error Group: Undefined License: GPLv2 URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) %description A library path is hardcoded to one of the following paths: /lib, /usr/lib. It should be replaced by something like /%{_lib} or %{_libdir}. %prep %autosetup %build /usr/lib/bash/dirname/ /usr/lib /lib %clean %files %defattr(-,root,root,-) %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/spec/hardcoded-packager-tag.spec000066400000000000000000000007211415540642600227200ustar00rootroot00000000000000Name: hardcoded-packager-tag Version: 0 Release: 0 Summary: hardcoded-packager-tag warning License: GPL-2.0-only Group: Undefined URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz Packager: Someone %description The Packager tag is hardcoded in your spec file. It should be removed, so as to use rebuilder's own defaults. %prep %build %install %files %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/spec/hardcoded-path-in-buildroot-tag.spec000066400000000000000000000010021415540642600244750ustar00rootroot00000000000000Name: hardcoded-path-in-buildroot-tag Version: 0 Release: 0 Summary: hardcoded-path-in-buildroot-tag warning. License: GPL-2.0-only Group: Undefined URL: http://rpmlint.zarb.org/#%{name} Buildroot: /usr/bin/bash Source0: Source0.tar.gz %description A path is hardcoded in your Buildroot tag. It should be replaced by something like %{_tmppath}/%{name}-%{version}-build. %prep %autosetup %build %install %files %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/spec/hardcoded-prefix-tag.spec000066400000000000000000000007151415540642600224430ustar00rootroot00000000000000Name: hardcoded-prefix-tag Version: 0 Release: 0 Summary: hardcoded-prefix-tag warning License: GPL-2.0-only Group: Undefined URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz Prefix: /usr/bin/bash %description The Prefix tag is hardcoded in your spec file. It should be removed, so as to allow package relocation. %prep %build %install %files %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/spec/lib-package-without-%mklibname.spec000066400000000000000000000007761415540642600243330ustar00rootroot00000000000000Name: SpecCheck34 Version: 0 Release: 0 Summary: None here Group: Undefined License: GPLv2 URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) %description SpecCheck test 34. %package -n %{libname} %prep %autosetup %build %install rm -rf $RPM_BUILD_ROOT %clean rm -rf $RPM_BUILD_ROOT %files %defattr(-,root,root,-) %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/spec/libdir-macro-in-noarch-package.spec000066400000000000000000000014621415540642600242730ustar00rootroot00000000000000Name: libdir-macro-in-noarch-package Version: 0 Release: 0 Summary: libdir-macro-in-noarch-packagew warning License: GPL-2.0-only Group: Undefined URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz BuildArch: noarch %description The %{_libdir} or %{_lib} macro was found in a noarch package in a section that gets included in binary packages. This is most likely an error because these macros are expanded on the build host and their values vary between architectures, probably resulting in a package that does not work properly on all architectures at runtime. Investigate whether the package is really architecture independent or if some other dir/macro should be instead. %prep %build %install %files %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/spec/macro-in-changelog-autochangelog.spec000066400000000000000000000014301415540642600247250ustar00rootroot00000000000000Name: macro-in-%changelog Version: 0 Release: 0 Summary: macro-in-%changelog warning License: GPL-2.0-only Group: Undefined URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz %description Macros are expanded in %changelog too, which can in unfortunate cases lead to the package not building at all, or other subtle unexpected conditions that affect the build. Even when that doesn't happen, the expansion results in possibly 'rewriting history' on subsequent package revisions and generally odd entries eg. in source rpms, which is rarely wanted. Avoid use of macros in %changelog altogether, or use two '%'s to escape them, like '%%foo'. %prep %build %install %files %{_libdir}/foo %changelog %autochangelog rpmlint-2.2.0+ds1/test/spec/macro-in-changelog.spec000066400000000000000000000014451415540642600221150ustar00rootroot00000000000000Name: macro-in-%changelog Version: 0 Release: 0 Summary: macro-in-%changelog warning License: GPL-2.0-only Group: Undefined URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz %description Macros are expanded in %changelog too, which can in unfortunate cases lead to the package not building at all, or other subtle unexpected conditions that affect the build. Even when that doesn't happen, the expansion results in possibly 'rewriting history' on subsequent package revisions and generally odd entries eg. in source rpms, which is rarely wanted. Avoid use of macros in %changelog altogether, or use two '%'s to escape them, like '%%foo'. %prep %build %install %files %{_libdir}/foo %changelog You have a %buildroot macro rpmlint-2.2.0+ds1/test/spec/macro-in-comment.spec000066400000000000000000000011341415540642600216230ustar00rootroot00000000000000Name: macro-in-comment Version: 0 Release: 0 Summary: macro-in-comment-warning Patch0: patch0.patch License: GPL-2.0-only Group: URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz %description There is a unescaped macro after a shell style comment in the specfile. Macros are expanded everywhere, so check if it can cause a problem in this case and escape the macro with another leading % if appropriate. %prep %autopatch %autosetup %build # this is a comment %{version} %install %files %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/spec/make-check-outside-check-section.spec000066400000000000000000000006631415540642600246430ustar00rootroot00000000000000Name: make-check-outside-check-section Version: 0 Release: 0 Summary: make-check-outside-check-section warning. Group: Undefined License: GPLv2 URL: http://rpmlint.zarb.org/#%{name} %description Make check or other automated regression test should be run in %check, as they can be disabled with a rpm macro for short circuiting purposes %prep make check %check %changelog rpmlint-2.2.0+ds1/test/spec/mixed-use-of-spaces-and-tabs.spec000066400000000000000000000010341415540642600237220ustar00rootroot00000000000000Name: mixed-use-of-spaces-and-tabs Version: 0 Release: 0 Summary: mixed-use-of-spaces-and-tabs warning License: GPL-2.0-only Group: Undefined URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz Patch0: Patch0.patch Requires: php %description The specfile mixes use of spaces and tabs for indentation, which is a cosmetic annoyance. %prep cd lib %autopatch %autosetup %build %install %ifarch # apply patch0 %patch0 -p0 %endif %files %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/spec/more-than-one-%changelog.spec000066400000000000000000000007241415540642600231250ustar00rootroot00000000000000Name: more-than-one-%changelog-section Version: 0 Release: 0 Summary: more-than-one-%changelog-section License: GPL-2.0-only Group: Undefined URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz %description The spec file unnecessarily contains more than one %changelog section. %prep %autosetup %build %install %files %{_libdir}/foo %changelog something. %changelog one another thing. rpmlint-2.2.0+ds1/test/spec/no-%%%s-section.spec000066400000000000000000000023231415540642600211540ustar00rootroot00000000000000Name: no-%%%s-section Version: 0 Release: 0 Summary: no-%%%s-section warning License: GPL-2.0-only Group: Undefined %description no-%prep-section:- The spec file does not contain a %prep section. Even if some packages don't directly need it, section markers may be overridden in rpm's configuration to provide additional 'under the hood' functionality. Add the section, even if empty. no-%build-section:- The spec file does not contain a %build section. Even if some packages don't directly need it, section markers may be overridden in rpm's configuration to provide additional 'under the hood' functionality, such as injection of automatic -debuginfo subpackages. Add the section, even if empty. no-%install-section:- The spec file does not contain an %install section. Even if some packages don't directly need it, section markers may be overridden in rpm's configuration to provide additional 'under the hood' functionality. Add the section, even if empty. no-%{clean}-section:- The spec file doesn't contain a %{clean} section to remove the files installed by the %install section %files %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/spec/no-buildroot-tag.spec000066400000000000000000000010751415540642600216500ustar00rootroot00000000000000Name: no-buildroot-tag Version: 0 Release: 0 Summary: no-buildroot-tag warning Group: Undefined License: GPLv2 %description The BuildRoot tag isn't used in your spec. It must be used in order to allow building the package as non root on some systems. For some rpm versions (e.g. rpm.org >= 4.6) the BuildRoot tag is not necessary in specfiles and is ignored by rpmbuild; if your package is only going to be built with such rpm versions you can ignore this warning. %files %defattr(-,root,root,-) %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/spec/non-break-space.spec000066400000000000000000000007441415540642600214310ustar00rootroot00000000000000Name: non-break-space Version: 0 Release: 0 Summary: non-break-space warning. License: GPL-2.0-only Group: Undefined URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz %description The spec file contains a non-break space, which looks like a regular space in some editors but can lead to obscure errors. It should be replaced by a regular space. %prep %build %install %files %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/spec/non-standard-group.spec000066400000000000000000000010301415540642600221730ustar00rootroot00000000000000Name: non-standard-group Version: 1.0 Release: 0 Summary: non-standard-group warning License: MIT Group: Something URL: https://www.example.com Source: Source.tar.gz BuildRequires: gcc %description A test specfile with Group (Group: Something) that is not standard. The value of the Group tag in the package is not valid. %prep %setup -q %build %configure %make_build %install %make_install %post %postun %files %license COPYING %doc ChangeLog README %changelog rpmlint-2.2.0+ds1/test/spec/non-utf8-spec-file.spec000066400000000000000000000014161415540642600220040ustar00rootroot00000000000000Name: non-utf8-spec-file Version: 0 Release: 0 Summary: non-utf8-spec-file warning License: GPL-2.0-only Group: Undefined URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz %description The character encoding of the spec file is not UTF-8. %prep %autosetup %build %install %files %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/spec/obsolete-tag.spec000066400000000000000000000007411415540642600210460ustar00rootroot00000000000000Name: obsolete-tag Version: 0 Release: 0 Summary: obsolete-tag warning. License: GPL-2.0-only Group: Undefined URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz Serial: 2 Copyright: Something %description The following tags are obsolete: Copyright and Serial. They must be replaced by License and Epoch respectively. %prep %autosetup %build %install %files %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/spec/patch-fuzz-is-changed.spec000066400000000000000000000013271415540642600225550ustar00rootroot00000000000000%define _default_patch_fuzz 2 Name: patch-fuzz-is-changed Version: 1.0 Release: 0 Summary: patch-fuzz-is-changed warning License: MIT URL: https://www.example.com Source: Source.tar.gz BuildRequires: gcc %description The internal patch fuzz value was changed, and could hide patchs issues, or could lead to applying a patch at the wrong location. Usually, this is often the sign that someone didn't check if a patch is still needed and do not want to rediff it. It is usually better to rediff the patch and try to send it %prep %setup -q %build %configure %make_build %install %make_install %post %postun %files %license COPYING %doc ChangeLog README %changelog rpmlint-2.2.0+ds1/test/spec/patch-not-applied.spec000066400000000000000000000005311415540642600217670ustar00rootroot00000000000000Name: patch-not-applied Version: 0 Release: 0 Summary: invalid-url warning License: GPL-2.0-only Group: Undefined Patch0: Patch.patch Patch1: Patch1.patch %description A patch is included in your package but was not applied. %prep %build %install %patch -P %files %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/spec/prereq_use.spec000066400000000000000000000012561415540642600206350ustar00rootroot00000000000000Name: prereq_use Version: 0 Release: 0 Summary: prereq_use warning License: GPL-2.0-only Group: Undefined URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz Patch0: Patch0.patch PreReq(pre): none PreReq(post): none_other %description The use of PreReq is deprecated. In the majority of cases, a plain Requires is enough and the right thing to do. Sometimes Requires(pre), Requires(post), Requires(preun) and/or Requires(postun) can also be used instead of PreReq. %prep cd lib %autopatch %autosetup %build %install %ifarch # apply patch0 %patch0 -p0 %endif %files %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/spec/rpm-buildroot-usage.spec000066400000000000000000000010331415540642600223550ustar00rootroot00000000000000Name: rpm-buildroot-usage Version: 0 Release: 0 Summary: rpm-buildroot-usage warning. Group: Undefined License: GPLv2 URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) %description $RPM_BUILD_ROOT should not be touched during %build or %prep stage, as it may break short circuit builds. %prep %buildroot %install %clean %files %defattr(-,root,root,-) %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/spec/setup-not-in-prep.spec000066400000000000000000000007331415540642600217700ustar00rootroot00000000000000Name: setup-not-in-prep Version: 0 Release: 0 Summary: setup-not-in-prep warning License: GPL-2.0-only Group: Undefined URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz %description The %setup macro should only be used within the %prep section because it may not expand to anything outside of it and can break the build in unpredictable. %setup %prep %build %files %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/spec/setup-not-quiet.spec000066400000000000000000000006461415540642600215500ustar00rootroot00000000000000Name: setup-not-quiet Version: 0 Release: 0 Summary: setup-not-quiet warning. License: GPL-2.0-only Group: Undefined URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz %description Use the -q option to the %setup macro to avoid useless build output from unpacking the sources. %prep %setup %build %install %files %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/spec/unversioned-explicit-obsoletes.spec000066400000000000000000000012641415540642600246370ustar00rootroot00000000000000Name: unversioned-explicit-obsoletes Version: 0 Release: 0 Summary: unversioned-explicit-obsoletes License: GPL-2.0-only Group: Undefined URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz Obsoletes: Something %description The specfile contains an unversioned Obsoletes: token, which will match all older, equal and newer versions of the obsoleted thing. This may cause update problems, restrict future package/provides naming, and may match something it was originally not inteded to match -- make the Obsoletes versioned if possible. %prep %autosetup %build %install %files %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/spec/unversioned-explicit-version.spec000066400000000000000000000015311415540642600243220ustar00rootroot00000000000000Name: unversioned-explicit-provides Version: 0 Release: 0 Summary: unversioned-explicit-provides warning. Group: Undefined License: GPLv2 URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) Provides: someones-something=%{version} %description The specfile contains an unversioned Provides: token, which will match all older, equal, and newer versions of the provided thing. This may cause update problems and will make versioned dependencies, obsoletions and conflicts on the provided thing useless -- make the Provides versioned if possible. %prep %autosetup %build %install rm -rf $RPM_BUILD_ROOT %clean rm -rf $RPM_BUILD_ROOT %files %defattr(-,root,root,-) %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/spec/use-of-RPM-SOURCE-DIR.spec000066400000000000000000000007751415540642600217340ustar00rootroot00000000000000Name: use-of-RPM-SOURCE-DIR Version: 0 Release: 0 Summary: use-of-RPM-SOURCE-DIR error License: GPL-2.0-only Group: Undefined URL: http://rpmlint.zarb.org/#%{name} Source0: Source0.tar.gz %description You use $RPM_SOURCE_DIR or %{_sourcedir} in your spec file. If you have to use a directory for building, use %{buildroot} instead. %prep %autosetup %build %{_sourcedir} %install rm -rf $RPM_SOURCE_DIR %files %{_libdir}/foo %changelog rpmlint-2.2.0+ds1/test/test_FHS.py000066400000000000000000000015101415540642600166770ustar00rootroot00000000000000import pytest from rpmlint.checks.FHSCheck import FHSCheck from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package @pytest.fixture(scope='function', autouse=True) def fhscheck(): CONFIG.info = True output = Filter(CONFIG) test = FHSCheck(CONFIG, output) return output, test @pytest.mark.parametrize('package', ['binary/non-fhs']) def test_FHS_compliance(tmpdir, package, fhscheck): """ Check that the directories are not FHS compliant. """ output, test = fhscheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) # Check invalid /usr subdirectory assert 'non-standard-dir-in-usr' in out assert 'sbin' in out # Check invalid /var subdirectory assert 'non-standard-dir-in-var' in out assert 'lib' in out rpmlint-2.2.0+ds1/test/test_LSB.py000066400000000000000000000021041415540642600166770ustar00rootroot00000000000000import pytest from rpmlint.checks.LSBCheck import LSBCheck from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package @pytest.fixture(scope='function', autouse=True) def lsbcheck(): CONFIG.info = True output = Filter(CONFIG) test = LSBCheck(CONFIG, output) return output, test @pytest.mark.parametrize('package', ['binary/fPing']) def test_LSB_compliance(tmpdir, package, lsbcheck): """ Check that the package name, version and release number are LSB compliant. """ output, test = lsbcheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) # Check invalid package name assert 'non-lsb-compliant-package-name' in out assert 'package name contains an illegal character' in out # Check invalid package version assert 'non-lsb-compliant-version' in out assert 'version number contains an illegal character' in out # Check invalid package release assert 'non-lsb-compliant-release' in out assert 'release number contains an illegal character' in out rpmlint-2.2.0+ds1/test/test_alternatives.py000066400000000000000000000060401415540642600207630ustar00rootroot00000000000000import pytest from rpmlint.checks.AlternativesCheck import AlternativesCheck from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package @pytest.fixture(scope='function', autouse=True) def alternativescheck(): CONFIG.info = True output = Filter(CONFIG) test = AlternativesCheck(CONFIG, output) return output, test # # udpate-alternatives tests # @pytest.mark.parametrize('package', ['binary/alternatives-ok']) def test_update_alternative_ok(tmpdir, package, alternativescheck): output, test = alternativescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'I: package supports update-alternatives' in out assert 'E' not in out assert 'W' not in out @pytest.mark.parametrize('package', ['binary/alternatives-borked']) def test_update_alternative_borked(tmpdir, package, alternativescheck): output, test = alternativescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: update-alternatives-requirement-missing' in out assert 'E: alternative-generic-name-not-symlink' in out assert 'E: alternative-link-not-ghost' in out assert 'E: update-alternatives-postun-call-missing' in out @pytest.mark.parametrize('package', ['binary/self']) def test_non_update_alternative_pkg(tmpdir, package, alternativescheck): output, test = alternativescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) # here we just check if there is no requirements checking on # non update-alternatived package assert 'E' not in out assert 'W' not in out @pytest.mark.parametrize('package', ['binary/python39-evtx']) def test_update_alternatives_correctness(tmpdir, package, alternativescheck): output, test = alternativescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: update-alternatives-postun-call-missing' not in out # # libalternatives tests # @pytest.mark.parametrize('package', ['binary/libalternatives-ok']) def test_libalternative_ok(tmpdir, package, alternativescheck): output, test = alternativescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'I: package supports libalternatives' in out assert 'E' not in out assert 'W' not in out @pytest.mark.parametrize('package', ['binary/libalternatives-borked']) def test_libalternative_borked(tmpdir, package, alternativescheck): output, test = alternativescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'I: package supports libalternatives' in out assert 'I: libalternatives-conf-not-found' in out assert 'E: alts-requirement-missed' in out assert 'E: libalternatives-directory-not-exist' in out assert 'E: empty-libalternatives-directory' in out assert 'W: man-entry-value-not-found' in out assert 'W: binary-entry-value-not-found' in out rpmlint-2.2.0+ds1/test/test_appdata.py000066400000000000000000000025041415540642600176750ustar00rootroot00000000000000from unittest.mock import patch import pytest from rpmlint.checks.AppDataCheck import AppDataCheck from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package, HAS_APPSTREAM_GLIB @pytest.fixture(scope='function', autouse=True) def appdatacheck(): CONFIG.info = True output = Filter(CONFIG) test = AppDataCheck(CONFIG, output) return output, test @pytest.mark.skipif(not HAS_APPSTREAM_GLIB, reason='Optional dependency appstream-glib not installed') @pytest.mark.parametrize('package', ['binary/appdata']) def test_appdata_fail(tmpdir, package, appdatacheck): output, test = appdatacheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) # there are two borked packages assert len(output.results) == 2 assert 'invalid-appdata-file' in out @pytest.mark.parametrize('package', ['binary/appdata']) @patch('rpmlint.checks.AppDataCheck.AppDataCheck.cmd', 'command-really-not-found') def test_appdata_fail_no_checker(tmpdir, package, appdatacheck): output, test = appdatacheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) # there is just one borked file as the other is invalid content # but valid xml assert len(output.results) == 1 assert 'invalid-appdata-file' in out rpmlint-2.2.0+ds1/test/test_bashisms.py000066400000000000000000000025761415540642600201050ustar00rootroot00000000000000import pytest from rpmlint.checks.BashismsCheck import BashismsCheck from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package, HAS_CHECKBASHISMS, HAS_DASH @pytest.fixture(scope='function', autouse=True) def bashismscheck(): CONFIG.info = True output = Filter(CONFIG) test = BashismsCheck(CONFIG, output) return output, test @pytest.mark.skipif(not HAS_CHECKBASHISMS, reason='Optional dependency checkbashisms not installed') @pytest.mark.skipif(not HAS_DASH, reason='Optional dependency dash not installed') @pytest.mark.parametrize('package', ['binary/bashisms']) def test_bashisms(tmpdir, package, bashismscheck): output, test = bashismscheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'W: potential-bashisms /bin/script1' in out assert 'W: bin-sh-syntax-error /bin/script2' in out @pytest.mark.skipif(not HAS_CHECKBASHISMS, reason='Optional dependency checkbashisms not installed') @pytest.mark.skipif(not HAS_DASH, reason='Optional dependency dash not installed') @pytest.mark.parametrize('package', ['binary/bashisms']) def test_bashisms_error(tmpdir, package, bashismscheck): output, test = bashismscheck package = get_tested_package(package, tmpdir) package.dirname = 'I-do-not-exist-for-sure' with pytest.raises(FileNotFoundError): test.check(package) rpmlint-2.2.0+ds1/test/test_binaries.py000066400000000000000000000266251415540642600200710ustar00rootroot00000000000000import pytest from rpmlint.checks.BinariesCheck import BinariesCheck from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package, IS_X86_64 @pytest.fixture(scope='function', autouse=True) def binariescheck(): CONFIG.info = True output = Filter(CONFIG) test = BinariesCheck(CONFIG, output) return output, test @pytest.mark.parametrize('package', ['binary/crypto-policy']) @pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') def test_forbidden_c_calls(tmpdir, package, binariescheck): output, test = binariescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'crypto-policy-non-compliance-openssl /usr/lib/cyrus-imapd/arbitron SSL_CTX_set_cipher_list' in out assert 'crypto-policy-non-compliance-openssl /usr/lib64/dovecot/libssl_iostream_openssl.so SSL_CTX_set_cipher_list' in out @pytest.mark.parametrize('package', ['binary/ngircd']) def test_waived_forbidden_c_calls(tmpdir, package, binariescheck): output, test = binariescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'crypto-policy-non-compliance' not in out @pytest.mark.parametrize('package', ['binary/libreiserfscore-devel']) def test_lto_bytecode(tmpdir, package, binariescheck): output, test = binariescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'lto-bytecode' in out @pytest.mark.parametrize('package', ['binary/lto-text']) def test_lto_archive_text(tmpdir, package, binariescheck): output, test = binariescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'lto-no-text-in-archive /usr/lib64/libiberty.a' in out assert 'lto-no-text-in-archive /usr/lib64/libdl_p.a' not in out @pytest.mark.parametrize('package', ['binary/ghc']) def test_lto_ghc_archive(tmpdir, package, binariescheck): output, test = binariescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'lto-no-text-in-archive' not in out @pytest.mark.parametrize('package', ['binary/libtool-wrapper']) def test_libtool_wrapper(tmpdir, package, binariescheck): output, test = binariescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: libtool-wrapper-in-package' in out assert 'W: unstripped-binary-or-object' in out assert 'E: arch-dependent-file-in-usr-share' in out assert 'W: unstripped-binary-or-object /bin/main' in out assert 'W: position-independent-executable-suggested /usr/share/main' in out @pytest.mark.parametrize('package', ['binary/noarch']) def test_no_arch_issues(tmpdir, package, binariescheck): output, test = binariescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: arch-independent-package-contains-binary-or-object /bin/main' in out assert 'E: noarch-with-lib64' in out @pytest.mark.parametrize('package', ['binary/libnoexec']) def test_shlib_with_no_exec(tmpdir, package, binariescheck): output, test = binariescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: shared-library-not-executable /lib64/libfoo.so' not in out @pytest.mark.parametrize('package', ['binary/glibc']) @pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') def test_shlib_with_no_exec_glibc(tmpdir, package, binariescheck): output, test = binariescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: shared-library-not-executable /lib64/libpthread.so' in out @pytest.mark.parametrize('package', ['binary/bcc-lua']) def test_position_independent_executable(tmpdir, package, binariescheck): CONFIG.configuration['PieExecutables'] = ['.*'] output = Filter(CONFIG) test = BinariesCheck(CONFIG, output) test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: non-position-independent-executable /usr/bin/bcc-lua' in out @pytest.mark.parametrize('package', ['binary/only-non-binary-in-usr-lib']) def test_only_non_binary_in_usr_lib(tmpdir, package, binariescheck): output, test = binariescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'W: only-non-binary-in-usr-lib' in out # there is a file in /usr/lib64, so no error assert 'E: no-binary' not in out # we have no 'noarch' or wrapper here assert 'E: noarch-with-lib64' not in out assert 'E: arch-independent-package-contains-binary-or-object' not in out assert 'E: libtool-wrapper-in-package' not in out # In general we want to throw a warning if we have only non-binary files in # the /usr/lib. But we can allow non-binaries via UsrLibBinaryException config # option. These files will be considered binaries and no warning should be # thrown. @pytest.mark.parametrize('package', ['binary/only-non-binary-in-usr-lib_exception']) def test_only_non_binary_in_usr_lib_exception(tmpdir, package, binariescheck): CONFIG.configuration['UsrLibBinaryException'] = '^/usr/lib(64)?/python' output = Filter(CONFIG) test = BinariesCheck(CONFIG, output) test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'W: only-non-binary-in-usr-lib' not in out @pytest.mark.parametrize('package', ['binary/no-binary']) def test_no_binary(tmpdir, package, binariescheck): output, test = binariescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: no-binary' in out # no .la file or binary there assert 'E: invalid-la-file' not in out assert 'E: binary-in-etc' not in out @pytest.mark.parametrize('package', ['binary/invalid-la-file']) def test_invalid_la_file(tmpdir, package, binariescheck): output, test = binariescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: invalid-la-file' in out # no /usr/share dir there assert 'E: arch-dependent-file-in-usr-share' not in out @pytest.mark.parametrize('package', ['binary/binary-in-etc']) def test_binary_in_etc(tmpdir, package, binariescheck): output, test = binariescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: binary-in-etc' in out # it's not a library package assert 'E: executable-in-library-package' not in out @pytest.mark.parametrize('package', ['binary/non-position-independent-exec']) def test_non_position_independent_sugg(tmpdir, package, binariescheck): # reset PieExecutable option CONFIG.configuration['PieExecutables'] = [] output = Filter(CONFIG) test = BinariesCheck(CONFIG, output) test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'W: position-independent-executable-suggested' in out # it should throw just a warning as it's not forced by PieExecutables opt assert 'E: non-position-independent-executable' not in out # Force an error by setting PieExecutables option to the no-pie binary @pytest.mark.parametrize('package', ['binary/non-position-independent-exec']) def test_non_position_independent(tmpdir, package, binariescheck): CONFIG.configuration['PieExecutables'] = ['sparta', '.*hello'] output = Filter(CONFIG) test = BinariesCheck(CONFIG, output) test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: non-position-independent-executable' in out # It should throw just the error, not warning assert 'W: position-independent-executable-suggested' not in out # libtest package @pytest.mark.parametrize('package', ['binary/libtest']) @pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') def test_library(tmpdir, package, binariescheck): output, test = binariescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: executable-in-library-package' in out assert 'W: no-soname' in out # there is no soname here so it can't be invalid assert 'E: invalid-soname' not in out # invalid-soname test package @pytest.mark.parametrize('package', ['binary/libtest1']) @pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') def test_shared_library1(tmpdir, package, binariescheck): output, test = binariescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: invalid-soname' in out # there is an invalid soname here, so no "no-soname" error assert 'W: no-soname' not in out # shlib-policy-name-error test package @pytest.mark.parametrize('package', ['binary/libtest2']) @pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') def test_shared_library2(tmpdir, package, binariescheck): output, test = binariescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: shlib-policy-name-error' in out # it doesn't call /sbin/ldconfig assert 'E: no-ldconfig-symlink' in out # no ldconfig is not invalid assert 'E: invalid-ldconfig-symlink' not in out # the soname is set assert 'W: no-soname' not in out # invalid-ldconfig-symlink test package @pytest.mark.parametrize('package', ['binary/libtest3']) @pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') def test_invalid_ldconfig_symlink(tmpdir, package, binariescheck): output, test = binariescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: invalid-ldconfig-symlink' in out # executable doesn't call mktemp, setuid or gethostbyname assert 'E: call-to-mktemp' not in out assert 'E: missing-call-to-setgroups-before-setuid' not in out assert 'W: binary-or-shlib-calls-gethostbyname' not in out # it's not statically linked either assert 'E: statically-linked-binary' not in out # valid symlink should not report invalid-ldconfig-symlink @pytest.mark.parametrize('package', ['binary/libtest4']) @pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') def test_not_valid_ldconfig_symlink(tmpdir, package, binariescheck): output, test = binariescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: invalid-ldconfig-symlink' not in out @pytest.mark.parametrize('package', ['binary/multiple_errors']) @pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') def test_multiple_errors(tmpdir, package, binariescheck): output, test = binariescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: call-to-mktemp' in out assert 'E: missing-call-to-setgroups-before-setuid' in out assert 'W: binary-or-shlib-calls-gethostbyname' in out @pytest.mark.parametrize('package', ['binary/dependency-info']) def test_dependency_information(tmpdir, package, binariescheck): output, test = binariescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: shared-library-without-dependency-information /usr/lib64/ruby/enc/gb2312.so' in out assert 'W: library-not-linked-against-libc /usr/lib64/ruby/continuation.so' in out rpmlint-2.2.0+ds1/test/test_build_date.py000066400000000000000000000022561415540642600203630ustar00rootroot00000000000000import re import pytest from rpmlint.checks.BuildDateCheck import BuildDateCheck from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package @pytest.fixture(scope='function', autouse=True) def builddatecheck(): CONFIG.info = True output = Filter(CONFIG) test = BuildDateCheck(CONFIG, output) return output, test @pytest.mark.parametrize('package', ['binary/builddate']) def test_build_date_time(tmpdir, package, builddatecheck): output, test = builddatecheck test.istoday = re.compile('Jan 1 2019') test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: file-contains-date-and-time /bin/with-datetime' in out assert 'E: file-contains-current-date /bin/with-date' in out @pytest.mark.parametrize('package', ['binary/bashisms']) def test_build_date_time_correct(tmpdir, package, builddatecheck): output, test = builddatecheck test.istoday = re.compile('Jan 1 2019') test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: file-contains-date-and-time' not in out assert 'E: file-contains-current-date' not in out rpmlint-2.2.0+ds1/test/test_build_root.py000066400000000000000000000013461415540642600204300ustar00rootroot00000000000000import pytest from rpmlint.checks.BuildRootCheck import BuildRootCheck from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package @pytest.fixture(scope='function', autouse=True) def buildrootcheck(): CONFIG.info = True output = Filter(CONFIG) test = BuildRootCheck(CONFIG, output) return output, test @pytest.mark.parametrize('package', ['binary/buildroot']) def test_build_root(tmpdir, package, buildrootcheck): output, test = buildrootcheck test.prepare_regex('/home/marxin/rpmbuild/BUILDROOT/%{NAME}-%{VERSION}-%{RELEASE}.x86_64') test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: file-contains-buildroot /bin/trace' in out rpmlint-2.2.0+ds1/test/test_cli.py000066400000000000000000000065321415540642600170370ustar00rootroot00000000000000from pathlib import PosixPath import pytest from rpmlint.cli import process_lint_args from rpmlint.config import Config from rpmlint.lint import Lint from Testing import HAS_CHECKBASHISMS, HAS_DASH @pytest.mark.parametrize('test_arguments', [['-c', 'rpmlint/configs/thisdoesntexist.toml']]) def test_parsing_non_existing_config_file(test_arguments): with pytest.raises(SystemExit) as exc: process_lint_args(test_arguments) assert exc.value.code == 2 @pytest.mark.parametrize('test_arguments', [['-c', 'rpmlint/configdefaults.toml']]) def test_parsing_config_file(test_arguments): parsed = process_lint_args(test_arguments) assert len(parsed['config']) == 1 assert parsed['config'][0] == PosixPath('rpmlint/configdefaults.toml') @pytest.mark.skipif(not HAS_CHECKBASHISMS, reason='Optional dependency checkbashisms not installed') @pytest.mark.skipif(not HAS_DASH, reason='Optional dependency dash not installed') @pytest.mark.parametrize('test_arguments', [['-c', 'configs/openSUSE']]) def test_parsing_opensuse_conf(test_arguments): parsed = process_lint_args(test_arguments) assert len(parsed['config']) == 7 assert PosixPath('configs/openSUSE/opensuse.toml') in parsed['config'] assert PosixPath('configs/openSUSE/licenses.toml') in parsed['config'] assert PosixPath('configs/openSUSE/pie-executables.toml') in parsed['config'] defaultcfg = Config() lint = Lint(parsed) default_checks = defaultcfg.configuration['Checks'] checks = lint.config.configuration['Checks'] # Verify that all original Checks are enabled and some new are added for check in default_checks: assert check in checks assert len(checks) > len(default_checks) # Verify that all scoring keys are a known checks checks = set(lint.output.error_details.keys()) checks |= set(defaultcfg.configuration['Descriptions'].keys()) score_keys = lint.config.configuration['Scoring'].keys() for score_key in score_keys: if score_key.startswith('percent-in-'): continue assert score_key in checks @pytest.mark.skipif(not HAS_CHECKBASHISMS, reason='Optional dependency checkbashisms not installed') @pytest.mark.skipif(not HAS_DASH, reason='Optional dependency dash not installed') @pytest.mark.parametrize('test_arguments', [['-c', 'configs/Fedora']]) def test_parsing_fedora_conf(test_arguments): parsed = process_lint_args(test_arguments) assert len(parsed['config']) == 5 assert PosixPath('configs/Fedora/fedora.toml') in parsed['config'] assert PosixPath('configs/Fedora/licenses.toml') in parsed['config'] assert PosixPath('configs/Fedora/users-groups.toml') in parsed['config'] defaultcfg = Config() lint = Lint(parsed) default_checks = defaultcfg.configuration['Checks'] checks = lint.config.configuration['Checks'] # Verify that all original Checks are enabled and some new are added for check in default_checks: assert check in checks assert len(checks) > len(default_checks) # Verify that all scoring keys are a known checks checks = set(lint.output.error_details.keys()) checks |= set(defaultcfg.configuration['Descriptions'].keys()) score_keys = lint.config.configuration['Scoring'].keys() for score_key in score_keys: if score_key.startswith('percent-in-'): continue assert score_key in checks rpmlint-2.2.0+ds1/test/test_config.py000066400000000000000000000111001415540642600175200ustar00rootroot00000000000000from pathlib import Path import pytest from rpmlint.config import Config from Testing import TEST_CONFIG, testpath TEST_CONFIG_2 = [testpath() / 'configs/test2.config'] TEST_CONFIG_FILTERS = [testpath() / 'configs/testfilters.config'] TEST_LIST1 = [testpath() / 'configs/testlists1.config'] TEST_LIST2 = [testpath() / 'configs/testlists2.config'] TEST_OVERRIDE = [testpath() / 'configs/test.override.config'] TEST_RPMLINTRC = testpath() / 'configs/testing-rpmlintrc' TEST_BROKEN = [testpath() / 'configs/broken.config'] def test_printing(capsys): cfg = Config() cfg.print_config() out, err = capsys.readouterr() assert not err assert out def test_custom_config(capsys): cfg = Config() # bullshit config cfg.find_configs([Path('BULLSHIT')]) out, err = capsys.readouterr() assert Path('BULLSHIT') not in cfg.conf_files assert 'BULLSHIT' in err # existing config cfg.find_configs(TEST_CONFIG) out, err = capsys.readouterr() assert cfg.conf_files assert not err def test_broken_config(capsys): with pytest.raises(SystemExit) as pytest_wrapped_e: Config(TEST_BROKEN) assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 4 def test_parsing(): # ugly content variables from old config bad_crypto_warning = \ """This application package calls a function to explicitly set crypto ciphers for SSL/TLS. That may cause the application not to use the system-wide set cryptographic policy and should be modified in accordance to: https://fedoraproject.org/wiki/Packaging:CryptoPolicies""" forbidden_functions = { 'crypto-policy-non-compliance-openssl': { 'f_name': 'SSL_CTX_set_cipher_list', 'description': bad_crypto_warning, }, 'crypto-policy-non-compliance-gnutls-1': { 'f_name': 'gnutls_priority_set_direct', 'description': bad_crypto_warning, }, 'crypto-policy-non-compliance-gnutls-2': { 'f_name': 'gnutls_priority_init', 'good_param': 'SYSLOG', 'description': bad_crypto_warning }, } cfg = Config(TEST_CONFIG) assert cfg.configuration assert cfg.configuration['Distribution'] == 'Fedora Project' assert cfg.configuration['WarnOnFunction'] == forbidden_functions # default value check assert cfg.configuration['UseDefaultRunlevels'] is True def test_double_config(): """ Load two configs and make sure we properly load all the values """ cfg = Config(TEST_CONFIG) assert len(cfg.conf_files) == 2 assert cfg.configuration['ExtraMenuNeeds'][0] == 'gnome' # shovel in another config cfg.load_config(TEST_CONFIG_2) assert len(cfg.conf_files) == 3 assert cfg.configuration['ExtraMenuNeeds'][-1] == 'windows' assert cfg.configuration['WarnOnFunction']['crypto-policy-non-compliance-openssl']['f_name'] == 'REPLACED' assert cfg.configuration['WarnOnFunction']['crypto-policy-3']['f_name'] == 'new_blobie' def test_filters(): """ Load some filters and make sure we generate nice regexp """ cfg = Config(TEST_CONFIG_FILTERS) assert len(cfg.configuration['Filters']) == 12 assert cfg.configuration['Filters'][0] == '.*invalid-buildhost.*' def test_list_merging(): """ Load two configs and check we loaded up in proper older with replacing based on TOML syntax """ cfg = Config(TEST_LIST1) assert len(cfg.configuration['Filters']) == 1 assert cfg.configuration['ValidGroups'][0] == 'bullshitgroup' cfg.load_config(TEST_LIST2) assert len(cfg.conf_files) == 3 assert len(cfg.configuration['Filters']) == 2 assert len(cfg.configuration['ValidGroups']) == 3 assert cfg.configuration['ValidGroups'][2] == 'System/Libraries' cfg.load_config(TEST_OVERRIDE) assert len(cfg.configuration['ValidGroups']) == 1 def test_badness_functions(): """ Test badness settings """ cfg = Config(TEST_CONFIG_2) assert len(cfg.configuration['Scoring']) == 0 cfg.set_badness('suse-dbus-unauthorized-service', 15) assert len(cfg.configuration['Scoring']) == 1 assert cfg.configuration['Scoring']['suse-dbus-unauthorized-service'] == 15 def test_rpmlint_loading(): """ Make sure we can load up rpmlintrc file without executing any code """ cfg = Config(TEST_CONFIG) cfg.load_rpmlintrc(TEST_RPMLINTRC) assert 'arch-independent-package-contains-binary-or-object ' in cfg.configuration['Filters'] assert len(cfg.configuration['Filters']) == 113 assert len(cfg.configuration['Scoring']) == 3 rpmlint-2.2.0+ds1/test/test_config_files.py000066400000000000000000000023711415540642600207140ustar00rootroot00000000000000import pytest from rpmlint.checks.ConfigFilesCheck import ConfigFilesCheck from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package @pytest.fixture(scope='function', autouse=True) def configfilescheck(): CONFIG.info = True output = Filter(CONFIG) test = ConfigFilesCheck(CONFIG, output) return output, test @pytest.mark.parametrize('package', ['binary/config-files']) def test_config_files(tmpdir, package, configfilescheck): output, test = configfilescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'non-etc-or-var-file-marked-as-conffile /usr/share/conffile3' in out assert 'conffile-without-noreplace-flag /etc/conffile1' in out assert 'conffile-without-noreplace-flag /var/conffile2' in out assert 'conffile-without-noreplace-flag /usr/share/conffile3' in out @pytest.mark.parametrize('package', ['binary/logrotate']) def test_config_files_correct(tmpdir, package, configfilescheck): output, test = configfilescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'non-etc-or-var-file-marked-as-conffile' not in out assert 'conffile-without-noreplace-flag' not in out rpmlint-2.2.0+ds1/test/test_dbus_policy.py000066400000000000000000000022521415540642600205770ustar00rootroot00000000000000import pytest from rpmlint.checks.DBusPolicyCheck import DBusPolicyCheck from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package @pytest.fixture(scope='function', autouse=True) def dbuspolicycheck(): CONFIG.info = True output = Filter(CONFIG) test = DBusPolicyCheck(CONFIG, output) return output, test @pytest.mark.parametrize('package', ['binary/dbusrule']) def test_dbus_policy(tmpdir, package, dbuspolicycheck): output, test = dbuspolicycheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: dbus-parsing-exception raised an exception: no element found: line 1, column 0 /etc/dbus-1/system.d/noxml.conf' in out assert 'E: dbus-policy-allow-without-destination ' in out assert 'W: dbus-policy-allow-receive ' in out assert 'E: dbus-policy-deny-without-destination ' in out assert 'E: communication not allowed /etc/dbus-1/system.d/org.freedesktop.NetworkManager2.conf' in out rpmlint-2.2.0+ds1/test/test_diff.py000066400000000000000000000033711415540642600171760ustar00rootroot00000000000000from rpmlint.rpmdiff import Rpmdiff from Testing import testpath def test_distribution_tags(): oldpkg = testpath() / 'binary/mc-4.8.15-10.3.1.x86_64.rpm' newpkg = testpath() / 'binary/mc-4.8.21-2.1.x86_64.rpm' ignore = [] diff = Rpmdiff(oldpkg, newpkg, ignore) textdiff = diff.textdiff() print(textdiff) # the count always reports one less assert 231 <= len(textdiff.splitlines()) <= 233 ignore.append('T') ignore.append('5') ignore.append('S') diff = Rpmdiff(oldpkg, newpkg, ignore) textdiff = diff.textdiff() print('----\n' + textdiff) assert 36 <= len(textdiff.splitlines()) <= 38 assert 'added /usr/share/mc/syntax/yaml.syntax' in textdiff def test_exclude(): oldpkg = testpath() / 'binary/mc-4.8.15-10.3.1.x86_64.rpm' newpkg = testpath() / 'binary/mc-4.8.21-2.1.x86_64.rpm' ignore = list('T5S') print(Rpmdiff(oldpkg, newpkg, ignore=ignore).textdiff()) for exclude in [], ['/usr/share/mc/ski'], ['/share/mc/skins'], ['skins']: diff = Rpmdiff(oldpkg, newpkg, ignore, exclude) textdiff = diff.textdiff() assert '/usr/share/mc/skins/yadt256.ini' in textdiff for exclude in (['/usr/share/mc/skins'], ['/usr/share/*/skins'], ['/*/*/*/skins']): diff = Rpmdiff(oldpkg, newpkg, ignore, exclude) textdiff = diff.textdiff() assert '/usr/share/mc/skins/yadt256.ini' not in textdiff assert '/usr/share/mc/syntax/cuda.syntax' in textdiff for exclude in ['*.syntax'], ['syntax/cuda.syntax']: diff = Rpmdiff(oldpkg, newpkg, ignore, exclude) textdiff = diff.textdiff() assert '/usr/share/mc/skins/yadt256.ini' in textdiff assert '/usr/share/mc/syntax/cuda.syntax' not in textdiff rpmlint-2.2.0+ds1/test/test_doc.py000066400000000000000000000030011415540642600170210ustar00rootroot00000000000000import pytest from rpmlint.checks.DocCheck import DocCheck from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package @pytest.fixture(scope='function', autouse=True) def doccheck(): CONFIG.info = True output = Filter(CONFIG) test = DocCheck(CONFIG, output) return output, test @pytest.mark.parametrize('package', ['binary/mydoc']) def test_doccheck(tmpdir, package, doccheck): output, test = doccheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: executable-docs /usr/share/doc/packages/mydoc/doc.html' in out assert 'E: executable-docs /usr/share/doc/packages/mydoc/README' in out assert 'W: package-with-huge-docs: 100%' in out @pytest.mark.parametrize('package', ['binary/doc-file-dependency']) def test_doc_file_dep(tmpdir, package, doccheck): output, test = doccheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'W: doc-file-dependency' in out assert 'W: install-file-in-docs' not in out @pytest.mark.parametrize('package', ['binary/install-file-in-docs']) def test_install_file_in_docs(tmpdir, package, doccheck): output, test = doccheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'W: install-file-in-docs' in out assert 'E: executable-docs' not in out assert 'W: package-with-huge-docs: 100%' not in out assert 'W: doc-file-dependency' not in out rpmlint-2.2.0+ds1/test/test_duplicates.py000066400000000000000000000025711415540642600204240ustar00rootroot00000000000000import pytest from rpmlint.checks.DuplicatesCheck import DuplicatesCheck from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package @pytest.fixture(scope='function', autouse=True) def duplicatescheck(): CONFIG.info = True output = Filter(CONFIG) test = DuplicatesCheck(CONFIG, output) return output, test @pytest.mark.parametrize('package', ['binary/duplicates']) def test_duplicates(tmpdir, package, duplicatescheck): output, test = duplicatescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: hardlink-across-partition /var/foo /etc/foo' in out assert 'E: hardlink-across-config-files /var/foo2 /etc/foo2' in out assert 'W: files-duplicate /etc/bar3 /etc/bar:/etc/bar2' in out assert 'W: files-duplicate /etc/strace2.txt /etc/strace1.txt' in out assert 'E: files-duplicated-waste 270516' in out @pytest.mark.parametrize('package', ['binary/bad-crc-uncompressed']) def test_duplicates_correct(tmpdir, package, duplicatescheck): output, test = duplicatescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: hardlink-across-partition' not in out assert 'E: hardlink-across-config-files' not in out assert 'W: files-duplicate' not in out assert 'E: files-duplicated-waste' not in out rpmlint-2.2.0+ds1/test/test_erlang.py000066400000000000000000000016071415540642600175360ustar00rootroot00000000000000from pkg_resources import get_distribution, parse_version import pytest from rpmlint.checks.ErlangCheck import ErlangCheck from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package @pytest.fixture(scope='function', autouse=True) def erlangcheck(): CONFIG.info = True output = Filter(CONFIG) test = ErlangCheck(CONFIG, output) return output, test @pytest.mark.skipif(get_distribution('pybeam').parsed_version < parse_version('0.7'), reason='pybeam >= 0.7 required') @pytest.mark.parametrize('package', ['binary/erlang-test']) def test_erlang(tmpdir, package, erlangcheck): output, test = erlangcheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: beam-compiled-without-debuginfo /usr/lib/erlang/m.beam' in out assert 'W: beam-compile-info-missed /usr/lib/erlang/m-no-CInf.beam' in out rpmlint-2.2.0+ds1/test/test_files.py000066400000000000000000000216371415540642600173750ustar00rootroot00000000000000import pytest from rpmlint.checks.FilesCheck import FilesCheck from rpmlint.checks.FilesCheck import pyc_magic_from_chunk, pyc_mtime_from_chunk from rpmlint.checks.FilesCheck import python_bytecode_to_script as pbts from rpmlint.checks.FilesCheck import script_interpreter as se from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package, get_tested_path @pytest.fixture(scope='function', autouse=True) def filescheck(): CONFIG.info = True output = Filter(CONFIG) test = FilesCheck(CONFIG, output) return output, test def test_pep3147(): assert pbts('/usr/lib64/python3.4/__pycache__/__phello__.foo.cpython-34.pyc') == '/usr/lib64/python3.4/__phello__.foo.py' assert pbts('/usr/lib64/python3.4/__pycache__/__phello__.foo.cpython-34.pyo') == '/usr/lib64/python3.4/__phello__.foo.py' def test_py2(): assert pbts('/usr/lib/python2.7/site-packages/_pytest/main.pyc') == '/usr/lib/python2.7/site-packages/_pytest/main.py' assert pbts('/usr/lib/python2.7/site-packages/_pytest/main.pyo') == '/usr/lib/python2.7/site-packages/_pytest/main.py' def test_pep0488(): assert pbts('/usr/lib/python3.5/site-packages/__pycache__/pytest.cpython-35.opt-1.pyc') == '/usr/lib/python3.5/site-packages/pytest.py' assert pbts('/usr/lib/python3.5/site-packages/__pycache__/pytest.cpython-35.opt-2.pyc') == '/usr/lib/python3.5/site-packages/pytest.py' assert pbts('/usr/lib/python3.5/site-packages/__pycache__/pytest.cpython-35.pyc') == '/usr/lib/python3.5/site-packages/pytest.py' def chunk_from_pyc(version, size=16): """Helper to get start of an example pyc file as bytes""" path = get_tested_path('pyc/__future__.cpython-{}.pyc'.format(version)) with open(path, 'rb') as f: return f.read(size) @pytest.mark.parametrize('package', ['binary/python3-power']) def test_python_bytecode_magic(tmpdir, package, filescheck): output, test = filescheck test.check(get_tested_package(package, tmpdir)) assert not output.results out = output.print_results(output.results) assert 'python-bytecode-wrong-magic-value' not in out @pytest.mark.parametrize('package', ['binary/testdocumentation']) def test_file_not_utf8_for_compression_algorithms(tmpdir, package, filescheck): output, test = filescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'file-not-utf8 /usr/share/doc/packages/testdocumentation/README1.gz' in out assert 'file-not-utf8 /usr/share/doc/packages/testdocumentation/README2.bz2' in out assert 'file-not-utf8 /usr/share/doc/packages/testdocumentation/README3.xz' in out @pytest.mark.parametrize('version, magic', ((36, 3379), (37, 3393))) def test_pyc_magic_from_chunk(version, magic): chunk = chunk_from_pyc(version) assert pyc_magic_from_chunk(chunk) == magic @pytest.mark.parametrize('version, mtime', ((36, 1513659236), (37, 1519778958))) def test_pyc_mtime_from_chunk(version, mtime): chunk = chunk_from_pyc(version) assert pyc_mtime_from_chunk(chunk) == mtime @pytest.mark.parametrize('package', ['binary/netmask-debugsource']) def test_devel_files(tmpdir, package, filescheck): output, test = filescheck test.check(get_tested_package(package, tmpdir)) assert len(output.results) == 5 out = output.print_results(output.results) assert 'devel-file-in-non-devel-package' not in out assert 'incorrect-fsf-address' in out assert 'no-documentation' in out @pytest.mark.parametrize('package', ['binary/makefile-junk']) def test_makefile_junk(tmpdir, package, filescheck): output, test = filescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'W: makefile-junk /usr/share/Makefile.am' in out assert out.count('W: makefile-junk') == 1 @pytest.mark.parametrize('package', ['binary/python3-greenlet']) def test_sphinx_inv_files(tmpdir, package, filescheck): output, test = filescheck test.check(get_tested_package(package, tmpdir)) assert not len(output.results) @pytest.mark.parametrize('package', ['binary/filechecks']) def test_invalid_package(tmpdir, package, filescheck): output, test = filescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'W: non-ghost-in-run /run/foo' in out assert 'W: systemd-unit-in-etc /etc/systemd/system/foo' in out assert 'W: udev-rule-in-etc /etc/udev/rules.d/foo' in out assert 'W: tmpfiles-conf-in-etc /etc/tmpfiles.d/foo' in out assert 'E: subdir-in-bin /bin/foo/bar' in out assert 'W: siteperl-in-perl-module /site_perl/foo' in out assert 'E: backup-file-in-package /~backup.rej' in out assert 'E: version-control-internal-file /.gitignore' in out assert 'E: htaccess-file /.htaccess' in out assert 'W: manifest-in-perl-module /usr/share/doc/perl-foo/MANIFEST' in out assert 'E: info-dir-file /usr/info/dir' in out @pytest.mark.parametrize('package', ['binary/tclpackage']) def test_tcl_package(tmpdir, package, filescheck): output, test = filescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: tcl-extension-file /usr/lib64/tcl/pkgIndex.tcl' in out def test_script_interpreter(): assert se(b'#!/bin/sh\n# Hello world!\n') == ('/bin/sh', '') assert se(b'#!/bin/bash -e\n') == ('/bin/bash', '-e') assert se(b'#! /usr/bin/perl -wT \n') == ('/usr/bin/perl', '-wT') assert se(b'#!/usr/bin/env python3 foo') == ('/usr/bin/env', 'python3 foo') assert se(b'# something here\n#!not a shebang') == (None, '') def test_scm_regex(): from rpmlint.checks.FilesCheck import scm_regex assert scm_regex.search('/foo/CVS/bar') assert scm_regex.search('/foo/RCS/bar') assert scm_regex.search('/bar/foo,v') assert scm_regex.search('bar/.svnignore') assert scm_regex.search('bar/.git/refs') def test_lib_regex(): from rpmlint.checks.FilesCheck import lib_regex # true matches assert all( lib_regex.search(x) for x in ('/lib/libnsl-2.26.so', '/usr/lib64/libgnomeui.so.3', '/lib64/libgcc_s.so.1')) # false positives assert not any( lib_regex.search(x) for x in ('/usr/share/gdb/auto-load/usr/lib/libglib-2.0.so.0.4600.1-gdb.py', '/usr/share/doc/findlib/lib-1.0.so', '/usr/lib64/libvulkan_radeon.so', '/usr/lib64/rsocket/binary',)) @pytest.mark.parametrize('package', ['binary/rust']) def test_rust_files(tmpdir, package, filescheck): output, test = filescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: wrong-script-interpreter /etc/foo.rs' in out assert 'E: wrong-script-interpreter /etc/bar.rs' not in out @pytest.mark.parametrize('package', ['binary/ngircd']) def test_distribution_tags(tmpdir, package, filescheck): output, test = filescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'manpage-not-compressed' in out assert 'This manual page is not compressed with the bz2 compression' in out @pytest.mark.parametrize('package', ['binary/development']) def test_provides_devel(tmpdir, package, filescheck): output, test = filescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: non-devel-file-in-devel-package /usr/x.typelib' in out @pytest.mark.parametrize('package', ['binary/shlib1']) def test_shlib1(tmpdir, package, filescheck): output, test = filescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'library-without-ldconfig-postin' in out assert 'library-without-ldconfig-postun' in out assert 'devel-file-in-non-devel-package' in out @pytest.mark.parametrize('package', ['binary/shlib2-devel']) def test_shlib2_devel(tmpdir, package, filescheck): output, test = filescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'library-without-ldconfig-postin' in out assert 'library-without-ldconfig-postun' in out assert 'non-devel-file-in-devel-package' in out @pytest.mark.parametrize('package', ['binary/file-zero-length']) @pytest.mark.parametrize( 'filename, show', [('/usr/lib/emptyfile', True), ('/usr/lib/nonemptyfile', False), ('/etc/security/console.apps', False), ('/usr/lib/.nosearch', False), ('/usr/lib/python/__init__.py', False), ('/usr/lib/python/py.typed', False), ('/usr/lib/python/pypackagefromwheel-0.0.0.dist-info/REQUESTED', False), ('/usr/lib/ruby/gem.build_complete', False)]) def test_zero_length_ignore(tmpdir, package, filescheck, filename, show): output, test = filescheck pkg = get_tested_package(package, tmpdir) test.check(pkg) out = output.print_results(output.results) assert filename in pkg.files assert (f'zero-length {filename}' in out) == show rpmlint-2.2.0+ds1/test/test_filter.py000066400000000000000000000176511415540642600175610ustar00rootroot00000000000000from pathlib import Path from rpmlint.config import Config from rpmlint.filter import Filter from Testing import get_tested_package, testpath TEST_CONFIG_FILTERS = [testpath() / 'configs/testfilters.config'] TEST_RPMLINTRC = testpath() / 'configs/testing-rpmlintrc' TEST3_RPMLINTRC = testpath() / 'configs/testing3-rpmlintrc' TEST_PACKAGE = Path('binary', 'ngircd') TEST_PACKAGE2 = Path('binary', 'tempfiled') TEST_DESCRIPTIONS = [testpath() / 'configs/descriptions.config'] def test_filters_regexp(): """ Load some filters and make sure we generate nice regexp """ cfg = Config(TEST_CONFIG_FILTERS) assert len(cfg.configuration['Filters']) == 12 assert cfg.configuration['Filters'][0] == '.*invalid-buildhost.*' def test_data_storing(tmpdir): """ Load some filters and make sure we generate nice regexp """ cfg = Config(TEST_CONFIG_FILTERS) cfg.load_rpmlintrc(TEST_RPMLINTRC) result = Filter(cfg) pkg = get_tested_package(TEST_PACKAGE, tmpdir) # this should be upgraded to error result.add_info('I', pkg, 'suse-other-error', '') assert len(result.results) == 1 assert result.printed_messages['I'] == 0 assert result.printed_messages['E'] == 1 # this should be downgraded result.add_info('E', pkg, 'suse-dbus-unauthorized-service', '') assert len(result.results) == 2 assert result.printed_messages['W'] == 1 assert result.printed_messages['E'] == 1 def test_data_storing_backward_compat(tmpdir): """ Make sure we can load some filters from rpmlintrc that worked with rpmlint v1. """ cfg = Config(TEST_CONFIG_FILTERS) cfg.load_rpmlintrc(TEST3_RPMLINTRC) parsed_filters = cfg.rpmlintrc_filters assert 'no-spaces-in-paren' in parsed_filters assert 'has-spaces-in-paren' in parsed_filters assert 'multiple-spaces-in-paren' in parsed_filters assert 'doublequotes-instead-of-singlequotes' in parsed_filters def test_description_storing(tmpdir): """ Test if we can store extra destcriptions and formatting is up par """ lorem_formated = """Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n\n""" cfg = Config(TEST_CONFIG_FILTERS) result = Filter(cfg) pkg = get_tested_package(TEST_PACKAGE, tmpdir) assert len(result.results) == 0 result.add_info('E', pkg, 'suse-dbus-unauthorized-service', '') # two options so we check the description is added only once result.add_info('I', pkg, 'suse-other-error', '/usr/bin/1') # nothing is populated assert not result.get_description('suse-other-error') # add descriptions result.error_details.update({'suse-other-error': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'}) assert result.get_description('suse-other-error') == lorem_formated def test_description_from_toml(tmpdir): """ Test if description loaded from toml shows up details """ cfg = Config(TEST_CONFIG_FILTERS) result = Filter(cfg) assert result.get_description('uncompressed-zip') assert result.get_description('uncompressed-zip') == 'The zip file is not compressed.\n\n' def test_description_from_conf(tmpdir): """ Test that descriptions strings are updated from configuration file. Load [Descriptions] from TEST_DESCRIPTIONS config file and test that the rpmlint error details are updated to the new values. """ cfg = Config(TEST_DESCRIPTIONS) result = Filter(cfg) assert result.get_description('no-binary', cfg) assert result.get_description('no-binary', cfg) == \ 'A new text for no-binary error.\n\n' assert result.get_description('no-soname', cfg) assert result.get_description('no-soname', cfg) == \ 'A new text for no-soname error.\n\n' # At this point, only basic descriptions from "descriptions" directory are # loaded. "Dynamic" descriptions that are defined directly in the check # file (e.g. see FHSCheck.py and its fhs_details_dict) are loaded later so # we can't test it now. It's tested in test_lint.py. assert not result.get_description('non-standard-dir-in-usr', cfg) assert not result.get_description('non-standard-dir-in-usr', cfg) == \ 'A new text for non-standard-dir-in-usr error.\n\n' assert not result.get_description('non-standard-dir-in-var', cfg) assert not result.get_description('non-standard-dir-in-var', cfg) == \ 'A new text for non-standard-dir-in-var error.\n\n' def test_output(tmpdir): """ Test the actual output of rpmlint on one file """ expected_output = """ngircd.x86_64: I: suse-other-error /usr/bin/1 ngircd.x86_64: I: suse-other-error /usr/bin/2 tempfiled.x86_64: E: suse-other-error /usr/bin/3 Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. ngircd.x86_64: E: suse-dbus-unauthorized-service\n""" cfg = Config(TEST_CONFIG_FILTERS) result = Filter(cfg) pkg = get_tested_package(TEST_PACKAGE, tmpdir) pkg2 = get_tested_package(TEST_PACKAGE2, tmpdir) # here we check if empty detail will not add whitespace result.add_info('E', pkg, 'suse-dbus-unauthorized-service', '') # two options so we check the description is added only once result.add_info('I', pkg, 'suse-other-error', '/usr/bin/1') result.add_info('I', pkg, 'suse-other-error', '/usr/bin/2') result.add_info('E', pkg2, 'suse-other-error', '/usr/bin/3') result.error_details.update({'suse-other-error': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'}) assert len(result.print_results(result.results).splitlines()) == 4 result.info = True assert len(result.print_results(result.results).splitlines()) == 11 assert result.print_results(result.results) == expected_output def test_filtered_output(tmpdir): cfg = Config(TEST_CONFIG_FILTERS) result = Filter(cfg) pkg = get_tested_package(TEST_PACKAGE, tmpdir) assert len(result.results) == 0 result.add_info('E', pkg, 'no-regex', '') result.add_info('E', pkg, 'no-regex-with-leading-space', '') result.add_info('E', pkg, 'bad-error', 'details of the error') result.add_info('E', pkg, 'test-color-error', 'details of the error') assert len(result.results) == 0 def test_blocked_filters(tmpdir): key = 'fatal-error' cfg = Config(TEST_CONFIG_FILTERS) result = Filter(cfg) pkg = get_tested_package(TEST_PACKAGE, tmpdir) assert len(result.results) == 0 assert key in cfg.configuration['Filters'] result.add_info('E', pkg, key, '') assert len(result.results) == 1 rpmlint-2.2.0+ds1/test/test_helpers.py000066400000000000000000000020101415540642600177150ustar00rootroot00000000000000from rpmlint import helpers def test_warnprint(capsys): """ Check we print stuff to stderr """ message = 'I am writing to stderr' helpers.print_warning(message) out, err = capsys.readouterr() assert message not in out assert message in err def test_bytetostr(): """ Test bytetostr function """ list_items = ( b'\xc5\xbe\xc3\xad\xc5\xbeala', 'texty', ) item = b'p\xc5\x99\xc3\xad\xc5\xa1ern\xc4\x9b \xc5\xbelu\xc5\xa5ou\xc4\x8dk\xc3\xbd k\xc5\xaf\xc5\x88' result = helpers.byte_to_string(item) assert isinstance(result, str) assert result == 'příšerně žluťoučký kůň' result = helpers.byte_to_string(list_items) assert isinstance(result, list) assert result[0] == 'žížala' def test_centering(capsys): """ Check wether centered print works """ message = 'Hello there' helpers.print_centered(message, '*') out, err = capsys.readouterr() assert '** Hello there **' in out assert not err rpmlint-2.2.0+ds1/test/test_icon_sizes.py000066400000000000000000000013141415540642600204260ustar00rootroot00000000000000import pytest from rpmlint.checks.IconSizesCheck import IconSizesCheck from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package @pytest.fixture(scope='function', autouse=True) def iconsizescheck(): CONFIG.info = True output = Filter(CONFIG) test = IconSizesCheck(CONFIG, output) return output, test @pytest.mark.parametrize('package', ['binary/tasque']) def test_icon_sizes(tmpdir, package, iconsizescheck): output, test = iconsizescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: wrong-icon-size /usr/share/tasque/icons/hicolor/16x16/status/tasque-note.png expected: 16x16 actual: 22x22' in out rpmlint-2.2.0+ds1/test/test_ldd_parser.py000066400000000000000000000071721415540642600204100ustar00rootroot00000000000000from pathlib import Path import pytest from rpmlint.checks.BinariesCheck import BinariesCheck from rpmlint.filter import Filter from rpmlint.lddparser import LddParser from rpmlint.pkg import FakePkg, get_magic from Testing import CONFIG, get_tested_path, IS_X86_64 @pytest.fixture(scope='function', autouse=True) def binariescheck(): CONFIG.info = True output = Filter(CONFIG) test = BinariesCheck(CONFIG, output) return output, test def get_full_path(path): return str(get_tested_path(Path('ldd', path))) def lddparser(path, system_path=None): if system_path is None: system_path = path return LddParser(get_full_path(path), system_path, True) def run_elf_checks(test, pkg, fullpath, path): test._detect_attributes(get_magic(fullpath)) test.run_elf_checks(pkg, fullpath, path) @pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') def test_unused_dependency(): ldd = lddparser('libtirpc.so.3.0.0') assert not ldd.parsing_failed_reason assert len(ldd.unused_dependencies) >= 1 assert 'liXXXsapi_krb5.so.2' in ldd.unused_dependencies @pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') def test_undefined_symbol(): ldd = lddparser('libtirpc.so.3.0.0') assert not ldd.parsing_failed_reason assert len(ldd.undefined_symbols) >= 22 assert 'GSS_C_NT_HOSTBASED_SERVICE' in ldd.undefined_symbols def test_ldd_parser_failure(): ldd = lddparser('not-existing-file') assert 'not-existing-file: No such file or directory' in ldd.parsing_failed_reason @pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') def test_dependencies(): ldd = lddparser('libtirpc.so.3.0.0') assert not ldd.parsing_failed_reason assert len(ldd.dependencies) == 5 assert any(d for d in ldd.dependencies if d.startswith('linux-vdso.so.1')) @pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') def test_unused_dependency_in_package(binariescheck): output, test = binariescheck run_elf_checks(test, FakePkg('fake'), get_full_path('libtirpc.so.3.0.0'), '/lib64/x.so') assert not test.readelf_parser.parsing_failed_reason() assert not test.ldd_parser.parsing_failed_reason out = output.print_results(output.results) assert 'E: unused-direct-shlib-dependency ' in out @pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') def test_unused_dependency_in_package_for_executable(binariescheck): output, test = binariescheck run_elf_checks(test, FakePkg('fake'), get_full_path('appletviewer'), '/usr/bin/appletviewer') assert not test.readelf_parser.parsing_failed_reason() assert not test.ldd_parser.parsing_failed_reason out = output.print_results(output.results) assert 'W: unused-direct-shlib-dependency ' in out @pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') def test_opt_dependency(binariescheck): output, test = binariescheck run_elf_checks(test, FakePkg('fake'), get_full_path('opt-dependency'), '/bin/opt-dependency') assert not test.readelf_parser.parsing_failed_reason() assert not test.ldd_parser.parsing_failed_reason out = output.print_results(output.results) assert 'E: linked-against-opt-library /bin/opt-dependency /opt/libfoo.so' in out @pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') def test_usr_dependency(binariescheck): output, test = binariescheck run_elf_checks(test, FakePkg('fake'), get_full_path('usr-dependency'), '/bin/usr-dependency') assert not test.readelf_parser.parsing_failed_reason() assert not test.ldd_parser.parsing_failed_reason out = output.print_results(output.results) assert 'W: linked-against-usr-library /bin/usr-dependency /usr/libfoo.so' in out rpmlint-2.2.0+ds1/test/test_lib_dependency.py000066400000000000000000000013241415540642600212260ustar00rootroot00000000000000import pytest from rpmlint.checks.LibraryDependencyCheck import LibraryDependencyCheck from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package @pytest.fixture(scope='function', autouse=True) def libdependencycheck(): CONFIG.info = True output = Filter(CONFIG) test = LibraryDependencyCheck(CONFIG, output) return output, test @pytest.mark.parametrize('package', ['binary/shlib2-devel']) def test_shlib2_devel(tmpdir, package, libdependencycheck): output, test = libdependencycheck test.check(get_tested_package(package, tmpdir)) test.after_checks() out = output.print_results(output.results) assert 'E: no-library-dependency-for /usr/lib/libfoo.so.1' in out rpmlint-2.2.0+ds1/test/test_lint.py000066400000000000000000000342331415540642600172350ustar00rootroot00000000000000from pathlib import Path import pytest from rpmlint.lint import Lint from rpmlint.spellcheck import ENCHANT from Testing import ( HAS_CHECKBASHISMS, HAS_DASH, HAS_ENGLISH_DICTIONARY, HAS_RPMDB, TEST_CONFIG, testpath ) TEST_RPMLINTRC = testpath() / 'configs/testing2-rpmlintrc' options_preset = { 'config': TEST_CONFIG, 'verbose': False, 'strict': False, 'permissive': False, 'print_config': False, 'explain': '', 'rpmfile': '', 'rpmlintrc': False, 'installed': '', 'time_report': False, 'profile': False } basic_tests = [ 'AlternativesCheck', 'AppDataCheck', 'BinariesCheck', 'BuildDateCheck', 'BuildRootCheck', 'ConfigFilesCheck', 'DBusPolicyCheck', 'DuplicatesCheck', 'DocCheck', 'ErlangCheck', 'FHSCheck', 'FilesCheck', 'IconSizesCheck', 'I18NCheck', 'LibraryDependencyCheck', 'LogrotateCheck', 'MenuCheck', 'MenuXDGCheck', 'MixedOwnershipCheck', 'PkgConfigCheck', 'PostCheck', 'SignatureCheck', 'SourceCheck', 'SpecCheck', 'TagsCheck', 'ZipCheck', 'ZyppSyntaxCheck', ] def _remove_except_zip(dictionary): """ In order to not lie in coverage redux the test run on the tests to just ZipCheck which has full coverage """ redux = {} redux['ZipCheck'] = dictionary['ZipCheck'] return redux def test_cases_loading(): linter = Lint(options_preset) assert list(linter.checks.keys()) == basic_tests def test_configoutput(capsys): additional_options = { 'print_config': True, } options = {**options_preset, **additional_options} linter = Lint(options) linter.run() out, err = capsys.readouterr() assert out assert 'Vendor = "Fedora Project"' in out assert 're.compile' not in out assert not err def test_explain_unknown(capsys): message = ['bullcrap'] additional_options = { 'explain': message, } options = {**options_preset, **additional_options} linter = Lint(options) linter.run() out, err = capsys.readouterr() assert 'bullcrap:\nUnknown message' in out assert not err def test_explain_known(capsys): message = ['infopage-not-compressed'] additional_options = { 'explain': message, } options = {**options_preset, **additional_options} linter = Lint(options) linter.run() out, err = capsys.readouterr() assert 'This info page is not compressed' in out assert 'Unknown message' not in out assert not err def test_explain_with_unknown(capsys): message = ['infopage-not-compressed', 'blablablabla'] additional_options = { 'explain': message, } options = {**options_preset, **additional_options} linter = Lint(options) linter.run() out, err = capsys.readouterr() assert 'This info page is not compressed' in out assert 'Unknown message' in out assert not err def test_explain_no_binary_from_cfg(capsys): """ Test that 'explain' option can read updated description from configuration. Test 'no-binary' error that is defined in CheckBinaries.toml file by default and then it's overridden to the custom values defined in 'descriptions.config' file. """ additional_options = { 'config': [testpath() / 'configs/descriptions.config'], 'explain': ['no-binary'] } options = {**options_preset, **additional_options} linter = Lint(options) linter.run() out, err = capsys.readouterr() # the new string is present and the old one is not assert 'A new text for no-binary error.' in out assert 'The package should be of the noarch architecture' not in out assert not err def test_explain_non_standard_dir_from_cfg(capsys): """ Test that 'explain' option can read updated description from configuration. Test 'non-standard-dir-in-usr' error that is special because the original description is not defined in FHSCheck.toml but in FHSCheck.py. Then it's supposed to be overridden to the custom values defined in 'descriptions.config' file. """ additional_options = { 'config': [testpath() / 'configs/descriptions.config'], 'explain': ['non-standard-dir-in-usr'] } options = {**options_preset, **additional_options} linter = Lint(options) linter.run() out, err = capsys.readouterr() assert 'A new text for non-standard-dir-in-usr error.' in out assert 'Your package is creating a non-standard subdirectory in /usr' not in out assert not err @pytest.mark.skipif(not ENCHANT, reason='Optional dependency pyenchant not install') @pytest.mark.skipif(not HAS_ENGLISH_DICTIONARY, reason='Missing English dictionary') @pytest.mark.parametrize('packages', [Path('test/binary/non-fhs-0-0.x86_64.rpm')]) def test_descriptions_from_config(capsys, packages): """ Test that rpmlint updates 'parametrized' descriptions from configuration. We test that "parametrized" errors (non-standard-dir-in-usr and non-standard-dir-in-var) were overridden by values from 'descriptions.config' file. """ additional_options = { 'config': [testpath() / 'configs/descriptions.config'], 'rpmfile': [packages] } options_preset['verbose'] = True options = {**options_preset, **additional_options} linter = Lint(options) linter.run() out, err = capsys.readouterr() assert 'A new text for non-standard-dir-in-usr error.' in out assert 'A new text for non-standard-dir-in-var error.' in out assert 'Your package is creating a non-standard subdirectory in /usr' \ not in out assert 'Your package is creating a non-standard subdirectory in /var' \ not in out assert not err @pytest.mark.parametrize('packages', [Path('test/source/wrongsrc-0-0.src.rpm')]) def test_run_single(capsys, packages): additional_options = { 'rpmfile': [packages], } options = {**options_preset, **additional_options} linter = Lint(options) linter.checks = _remove_except_zip(linter.checks) linter.run() out, err = capsys.readouterr() assert '1 packages and 0 specfiles checked' in out assert not err @pytest.mark.skipif(not HAS_RPMDB, reason='No RPM database present') @pytest.mark.parametrize('packages', [Path('test/source/wrongsrc-0-0.src.rpm')]) def test_run_installed(capsys, packages): # load up 1 normal path file and 2 installed packages additional_options = { 'rpmfile': [packages], 'installed': ['binutils', 'rpm'], } options = {**options_preset, **additional_options} linter = Lint(options) linter.checks = _remove_except_zip(linter.checks) linter.run() out, err = capsys.readouterr() assert '3 packages and 0 specfiles checked' in out assert not err @pytest.mark.parametrize('packages', [Path('test/binary/ruby2.5-rubygem-rubyzip-testsuite-1.2.1-0.x86_64.rpm')]) def test_run_strict(capsys, packages): """ Test if we convert warning to error """ additional_options = { 'rpmfile': [packages], 'strict': True, } options = {**options_preset, **additional_options} linter = Lint(options) linter.checks = _remove_except_zip(linter.checks) linter.run() out, err = capsys.readouterr() assert 'W: unable-to-read-zip' not in out assert 'E: unable-to-read-zip' in out assert not err @pytest.mark.skipif(not HAS_RPMDB, reason='No RPM database present') def test_run_installed_not_present(capsys): additional_options = { 'rpmfile': [], 'installed': ['non-existing-package'], } options = {**options_preset, **additional_options} linter = Lint(options) linter.checks = _remove_except_zip(linter.checks) linter.run() out, err = capsys.readouterr() assert '0 packages and 0 specfiles checked' in out assert 'there is no installed rpm' in err assert 'There are no files to process' in err @pytest.mark.skipif(not HAS_RPMDB, reason='No RPM database present') def test_run_installed_and_no_files(capsys): additional_options = { 'rpmfile': [], 'installed': ['rpm'], } options = {**options_preset, **additional_options} linter = Lint(options) linter.checks = _remove_except_zip(linter.checks) linter.run() out, err = capsys.readouterr() assert '1 packages and 0 specfiles checked' in out assert not err @pytest.mark.skipif(not HAS_RPMDB, reason='No RPM database present') def test_header_information(capsys): additional_options = { 'rpmfile': [], 'installed': ['python3-rpm'], } options = {**options_preset, **additional_options} linter = Lint(options) linter.checks = _remove_except_zip(linter.checks) linter.run() out, err = capsys.readouterr() assert 'packages: 1' in out @pytest.mark.skipif(not HAS_CHECKBASHISMS, reason='Optional dependency checkbashisms not installed') @pytest.mark.skipif(not HAS_DASH, reason='Optional dependency dash not installed') @pytest.mark.parametrize('packages', [list(Path('test').glob('*/*.rpm'))]) @pytest.mark.parametrize('configs', [list(Path('configs').glob('*/*.toml'))]) @pytest.mark.no_cover def test_run_full_rpm(capsys, packages, configs): number_of_pkgs = len(packages) additional_options = { 'rpmfile': packages, } options_preset['config'] = configs options = {**options_preset, **additional_options} linter = Lint(options) linter.run() out, err = capsys.readouterr() assert f'{number_of_pkgs} packages and 0 specfiles checked' in out # we convert the err as we don't care about errors from missing # spellchecking dictionaries -> we have to ignore it err_reduced = [a for a in err.split('\n') if not a.startswith('(none): W: unable to load spellchecking dictionary for') and a != ''] # also we can find out signatures are wrong because of the other distros # could've signed it err_reduced = [a for a in err_reduced if not a.startswith('Error checking signature of')] assert not err_reduced @pytest.mark.skipif(not HAS_CHECKBASHISMS, reason='Optional dependency checkbashisms not installed') @pytest.mark.skipif(not HAS_DASH, reason='Optional dependency dash not installed') @pytest.mark.parametrize('packages', [list(Path('test/spec').glob('*.spec'))]) @pytest.mark.parametrize('configs', [list(Path('configs').glob('*/*.toml'))]) @pytest.mark.no_cover def test_run_full_specs(capsys, packages, configs): number_of_pkgs = len(packages) additional_options = { 'rpmfile': packages, } options_preset['config'] = configs options = {**options_preset, **additional_options} linter = Lint(options) linter.run() out, err = capsys.readouterr() assert f'0 packages and {number_of_pkgs} specfiles checked' in out assert not err @pytest.mark.skipif(not HAS_CHECKBASHISMS, reason='Optional dependency checkbashisms not installed') @pytest.mark.skipif(not HAS_DASH, reason='Optional dependency dash not installed') @pytest.mark.parametrize('packages', [Path('test/spec')]) @pytest.mark.no_cover def test_run_full_directory(capsys, packages): assert packages.is_dir() file_list = [] for item in packages.iterdir(): if item.is_file(): file_list.append(item) number_of_pkgs = len(file_list) additional_options = { 'rpmfile': [packages], } options = {**options_preset, **additional_options} linter = Lint(options) linter.run() out, err = capsys.readouterr() assert f'0 packages and {number_of_pkgs} specfiles checked' in out assert not err @pytest.mark.skipif(not HAS_CHECKBASHISMS, reason='Optional dependency checkbashisms not installed') @pytest.mark.skipif(not HAS_DASH, reason='Optional dependency dash not installed') def test_run_empty(capsys): linter = Lint(options_preset) linter.run() out, err = capsys.readouterr() assert err assert '0 packages and 0 specfiles checked; 0 errors, 0 warnings' in out @pytest.mark.skipif(not HAS_CHECKBASHISMS, reason='Optional dependency checkbashisms not installed') @pytest.mark.skipif(not HAS_DASH, reason='Optional dependency dash not installed') @pytest.mark.parametrize('packages', [Path('test/rpmlintrc/single')]) def test_run_rpmlintrc_single_dir(capsys, packages): additional_options = { 'rpmfile': [packages], } options = {**options_preset, **additional_options} linter = Lint(options) linter.run() out, err = capsys.readouterr() assert not err assert 'rpmlintrc:' in out @pytest.mark.skipif(not HAS_CHECKBASHISMS, reason='Optional dependency checkbashisms not installed') @pytest.mark.skipif(not HAS_DASH, reason='Optional dependency dash not installed') @pytest.mark.parametrize('packages', [Path('test/rpmlintrc/multiple')]) def test_run_rpmlintrc_multiple(capsys, packages): additional_options = { 'rpmfile': [packages], } options = {**options_preset, **additional_options} linter = Lint(options) linter.run() out, err = capsys.readouterr() assert 'rpmlintrc:' not in out assert 'There are multiple items to be loaded for rpmlintrc' in err assert '0 badness' in out @pytest.mark.skipif(not HAS_CHECKBASHISMS, reason='Optional dependency checkbashisms not installed') @pytest.mark.skipif(not HAS_DASH, reason='Optional dependency dash not installed') @pytest.mark.parametrize('packages', [Path('test/rpmlintrc/single/sample.spec')]) def test_run_rpmlintrc_single_file(capsys, packages): additional_options = { 'rpmfile': [packages], 'rpmlintrc': TEST_RPMLINTRC } options = {**options_preset, **additional_options} linter = Lint(options) linter.run() out, err = capsys.readouterr() assert not err assert 'rpmlintrc:' in out assert 'E: unused-rpmlintrc-filter "I am not used"' in out assert 'E: unused-rpmlintrc-filter "She is not used"' not in out assert 'no-%build-section' not in out @pytest.mark.skipif(not HAS_RPMDB, reason='No RPM database present') def test_installed_package(capsys): additional_options = { 'installed': ['bzip2'], 'permissive': True } options = {**options_preset, **additional_options} linter = Lint(options) retcode = linter.run() out, err = capsys.readouterr() assert '1 packages and 0 specfiles checked' in out assert retcode == 0 rpmlint-2.2.0+ds1/test/test_logrotate.py000066400000000000000000000015571415540642600202720ustar00rootroot00000000000000import pytest from rpmlint.checks.LogrotateCheck import LogrotateCheck from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package @pytest.fixture(scope='function', autouse=True) def logrotatecheck(): CONFIG.info = True output = Filter(CONFIG) test = LogrotateCheck(CONFIG, output) return output, test @pytest.mark.parametrize('package', ['binary/logrotate']) def test_logrotate(tmpdir, package, logrotatecheck): output, test = logrotatecheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: logrotate-log-dir-not-packaged /var/log/myapp' in out assert 'E: logrotate-duplicate /var/log/myapp' in out assert 'E: logrotate-user-writable-log-dir /tmp/foo marxin:users 0755' in out assert 'E: logrotate-user-writable-log-dir /tmp/foo2 root:users2 0777' in out rpmlint-2.2.0+ds1/test/test_menuxdg.py000066400000000000000000000057051415540642600177400ustar00rootroot00000000000000import pytest from rpmlint.checks.MenuXDGCheck import MenuXDGCheck from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package, HAS_DESKTOP_FILE_UTILS @pytest.fixture(scope='function', autouse=True) def menuxdgcheck(): CONFIG.info = True output = Filter(CONFIG) test = MenuXDGCheck(CONFIG, output) return output, test @pytest.mark.skipif(not HAS_DESKTOP_FILE_UTILS, reason='Optional dependency desktop-file-utils not installed') @pytest.mark.parametrize('package', ['binary/menuxdg1']) def test_raises_parse_error(tmpdir, package, menuxdgcheck): output, test = menuxdgcheck test.check(get_tested_package(package, tmpdir)) assert len(output.results) == 4 out = output.print_results(output.results) assert 'contains parsing error' in out assert ' invalid-desktopfile ' in out assert 'check with desktop-file-validate' in out @pytest.mark.skipif(not HAS_DESKTOP_FILE_UTILS, reason='Optional dependency desktop-file-utils not installed') @pytest.mark.parametrize('package', ['binary/desktopfile-bad-binary']) def test_without_binary(tmpdir, package, menuxdgcheck): output, test = menuxdgcheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'desktopfile-without-binary' in out @pytest.mark.skipif(not HAS_DESKTOP_FILE_UTILS, reason='Optional dependency desktop-file-utils not installed') @pytest.mark.parametrize('package', ['binary/desktopfile-bad-duplicate']) def test_duplicate(tmpdir, package, menuxdgcheck): output, test = menuxdgcheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'desktopfile-duplicate-section' in out assert 'invalid-desktopfile' in out @pytest.mark.skipif(not HAS_DESKTOP_FILE_UTILS, reason='Optional dependency desktop-file-utils not installed') @pytest.mark.parametrize('package', ['binary/desktopfile-bad-section']) def test_missing_header(tmpdir, package, menuxdgcheck): output, test = menuxdgcheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'desktopfile-missing-header' in out assert 'invalid-desktopfile' in out @pytest.mark.skipif(not HAS_DESKTOP_FILE_UTILS, reason='Optional dependency desktop-file-utils not installed') @pytest.mark.parametrize('package', ['binary/desktopfile-bad-unicode']) def test_bad_unicode(tmpdir, package, menuxdgcheck): output, test = menuxdgcheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'non-utf8-desktopfile' in out @pytest.mark.skipif(not HAS_DESKTOP_FILE_UTILS, reason='Optional dependency desktop-file-utils not installed') @pytest.mark.parametrize('package', ['binary/desktopfile-good']) def test_good(tmpdir, package, menuxdgcheck): output, test = menuxdgcheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert not out rpmlint-2.2.0+ds1/test/test_mixed_ownership.py000066400000000000000000000016711415540642600214730ustar00rootroot00000000000000import pytest from rpmlint.checks.MixedOwnershipCheck import MixedOwnershipCheck from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package @pytest.fixture(scope='function', autouse=True) def mixedownershipcheck(): CONFIG.info = True output = Filter(CONFIG) test = MixedOwnershipCheck(CONFIG, output) return output, test @pytest.mark.parametrize('package', ['binary/mixed-ownership']) def test_mixed_ownership(tmpdir, package, mixedownershipcheck): output, test = mixedownershipcheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'noproblem' not in out assert 'file-parent-ownership-mismatch Path "/var/lib/badfolder/broken1" owned by "root" is stored in directory owned by "nobody"' in out assert 'file-parent-ownership-mismatch Path "/var/lib/badfolder/correctperms" owned by "root" is stored in directory owned by "nobody"' in out rpmlint-2.2.0+ds1/test/test_objdump_parser.py000066400000000000000000000033111415540642600212740ustar00rootroot00000000000000from pathlib import Path import pytest from rpmlint.checks.BinariesCheck import BinariesCheck from rpmlint.filter import Filter from rpmlint.objdumpparser import ObjdumpParser from rpmlint.pkg import FakePkg, get_magic from Testing import CONFIG, get_tested_path, IS_X86_64 @pytest.fixture(scope='function', autouse=True) def binariescheck(): print(CONFIG) CONFIG.info = True output = Filter(CONFIG) test = BinariesCheck(CONFIG, output) return output, test def get_full_path(path): return str(get_tested_path(Path('readelf', path))) def objdumpparser(path, system_path=None): if system_path is None: system_path = path return ObjdumpParser(get_full_path(path), system_path) def run_elf_checks(test, pkg, fullpath, path): test._detect_attributes(get_magic(fullpath)) test.run_elf_checks(pkg, fullpath, path) def test_basic(): objdump = objdumpparser('executable-stack', '/lib64/executable-stack') assert not objdump.parsing_failed_reason assert len(objdump.compile_units) == 5 first = objdump.compile_units[0] assert first['name'] == '../sysdeps/x86_64/start.S' assert first['comp_dir'] == '/home/abuild/rpmbuild/BUILD/glibc-2.29/csu' assert first['producer'] == 'GNU AS 2.32' assert first['language'] == '32769\t(MIPS assembler)' @pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') def test_executable_stack_package(binariescheck): output, test = binariescheck run_elf_checks(test, FakePkg('fake'), get_full_path('executable-stack'), 'a.out') out = output.print_results(output.results) assert 'W: missing-mandatory-optflags a.out -fno-PIE -g -Ofast' in out assert 'E: forbidden-optflags a.out -frounding-math' in out rpmlint-2.2.0+ds1/test/test_pam_modules.py000066400000000000000000000012171415540642600205700ustar00rootroot00000000000000import pytest from rpmlint.checks.PAMModulesCheck import PAMModulesCheck from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package @pytest.fixture(scope='function', autouse=True) def pammodulecheck(): CONFIG.info = True output = Filter(CONFIG) test = PAMModulesCheck(CONFIG, output) return output, test @pytest.mark.parametrize('package', ['binary/pam-module']) def test_pam_modules(tmpdir, package, pammodulecheck): output, test = pammodulecheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: pam-unauthorized-module pam-module.so' in out rpmlint-2.2.0+ds1/test/test_pkg.py000066400000000000000000000011361415540642600170440ustar00rootroot00000000000000import rpm from rpmlint.pkg import parse_deps, rangeCompare def test_parse_deps(): for (arg, exp) in ( ('a, b < 1.0 c = 5:2.0-3 d', [('a', 0, (None, None, None)), ('b', rpm.RPMSENSE_LESS, (None, '1.0', None)), ('c', rpm.RPMSENSE_EQUAL, ('5', '2.0', '3')), ('d', 0, (None, None, None))]), ): assert parse_deps(arg) == exp def test_range_compare(): for (req, prov) in ( (('foo', rpm.RPMSENSE_LESS, (None, '1.0', None)), ('foo', rpm.RPMSENSE_EQUAL, ('1', '0.5', None))), ): assert not rangeCompare(req, prov) rpmlint-2.2.0+ds1/test/test_pkgconfig.py000066400000000000000000000023711415540642600202340ustar00rootroot00000000000000import pytest from rpmlint.checks.PkgConfigCheck import PkgConfigCheck from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package @pytest.fixture(scope='function', autouse=True) def pkgconfigcheck(): CONFIG.info = True output = Filter(CONFIG) test = PkgConfigCheck(CONFIG, output) return output, test @pytest.mark.parametrize('package', ['binary/pc']) def test_pkg_config(tmpdir, package, pkgconfigcheck): output, test = pkgconfigcheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: invalid-pkgconfig-file /tmp/pkgconfig/xcb.pc' in out assert 'E: pkgconfig-invalid-libs-dir /tmp/pkgconfig/xcb.pc Libs: -L/usr/lib' in out assert 'E: double-slash-in-pkgconfig-path /tmp/pkgconfig/xcb.pc includedir=/usr/include//xyz' in out @pytest.mark.parametrize('package', ['binary/libreiserfscore-devel']) def test_pkg_config_correct(tmpdir, package, pkgconfigcheck): output, test = pkgconfigcheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: invalid-pkgconfig-file' not in out assert 'E: pkgconfig-invalid-libs-dir' not in out assert 'E: double-slash-in-pkgconfig-path' not in out rpmlint-2.2.0+ds1/test/test_readelf_parser.py000066400000000000000000000235231415540642600212450ustar00rootroot00000000000000from pathlib import Path import re import pytest from rpmlint.checks.BinariesCheck import BinariesCheck from rpmlint.filter import Filter from rpmlint.pkg import FakePkg, get_magic from rpmlint.readelfparser import ReadelfParser from Testing import CONFIG, get_tested_path, HAS_32BIT_GLIBC, IS_I686, IS_X86_64 @pytest.fixture(scope='function', autouse=True) def binariescheck(): CONFIG.info = True output = Filter(CONFIG) test = BinariesCheck(CONFIG, output) return output, test def get_full_path(path): return str(get_tested_path(Path('readelf', path))) def readelfparser(path, system_path=None): if system_path is None: system_path = path return ReadelfParser(get_full_path(path), system_path) def run_elf_checks(test, pkg, fullpath, path): test._detect_attributes(get_magic(fullpath)) test.run_elf_checks(pkg, fullpath, path) def test_empty_archive(): readelf = readelfparser('empty-archive.a') assert len(readelf.section_info.elf_files) == 0 assert len(readelf.symbol_table_info.symbols) == 0 def test_simple_archive(): readelf = readelfparser('main.a') assert readelf.is_archive assert len(readelf.section_info.elf_files) == 1 elf_file = readelf.section_info.elf_files[0] assert len(elf_file) == 11 assert elf_file[0].name == '.text' assert elf_file[0].size == 21 assert len(readelf.symbol_table_info.symbols) == 3 sym0 = readelf.symbol_table_info.symbols[0] assert sym0.name == 'main.c' assert sym0.type == 'FILE' assert sym0.bind == 'LOCAL' assert sym0.visibility == 'DEFAULT' sym1 = readelf.symbol_table_info.symbols[1] assert sym1.name == 'main' assert sym1.type == 'FUNC' assert sym1.bind == 'GLOBAL' assert sym1.visibility == 'DEFAULT' assert len(list(readelf.symbol_table_info.get_functions_for_regex(re.compile('mai.')))) == 1 def test_program_header_parsing(): readelf = readelfparser('nested-function') assert len(readelf.program_header_info.headers) == 11 h0 = readelf.program_header_info.headers[0] assert h0.name == 'PHDR' assert h0.flags == 'R' h9 = readelf.program_header_info.headers[9] assert h9.name == 'GNU_STACK' assert h9.flags == 'RWE' def test_dynamic_section_parsing(): readelf = readelfparser('libutil-2.29.so', '/lib64/libutil-2.29.so') assert readelf.is_shlib assert not readelf.is_archive sections = readelf.dynamic_section_info.sections assert len(sections) == 30 assert sections[0].key == 'NEEDED' assert sections[0].value == 'Shared library: [libc.so.6]' assert readelf.dynamic_section_info['SYMTAB'] == ['0x4c8'] assert readelf.dynamic_section_info['NULL'] == ['0x0'] assert readelf.dynamic_section_info.soname == 'libutil.so.1' assert len(readelf.dynamic_section_info.needed) == 1 assert readelf.dynamic_section_info.needed[0] == 'libc.so.6' def test_rpath(): readelf = readelfparser('rpath-lib.so', '/lib64/rpath-lib.so') assert readelf.is_shlib assert not readelf.is_archive assert len(readelf.dynamic_section_info.runpath) == 1 assert '/tmp/termcap.so.4' in readelf.dynamic_section_info.runpath def test_lto_bytecode(binariescheck): output, test = binariescheck run_elf_checks(test, FakePkg('fake'), get_full_path('lto-object.o'), 'x.a') assert not test.readelf_parser.parsing_failed_reason() out = output.print_results(output.results) assert 'lto-bytecode' in out def test_lto_archive_text(binariescheck): output, test = binariescheck run_elf_checks(test, FakePkg('fake'), get_full_path('stripped-lto.a'), 'x.a') out = output.print_results(output.results) assert 'E: lto-no-text-in-archive' in out assert 'E: static-library-without-debuginfo' in out def test_stripped_archive(binariescheck): output, test = binariescheck run_elf_checks(test, FakePkg('fake'), get_full_path('stripped-archive.a'), 'x.a') out = output.print_results(output.results) assert 'E: static-library-without-symtab' in out def test_lto_archive_text_function_sections(binariescheck): output, test = binariescheck run_elf_checks(test, FakePkg('fake'), get_full_path('function-sections.a'), 'x.a') assert 'E: lto-no-text-in-archive' not in output.print_results(output.results) def test_lto_archive_init_array(binariescheck): output, test = binariescheck run_elf_checks(test, FakePkg('fake'), get_full_path('libbsd-ctor.a'), 'x.a') assert 'E: lto-no-text-in-archive' not in output.print_results(output.results) def test_lto_archive_preinit_array(binariescheck): output, test = binariescheck run_elf_checks(test, FakePkg('fake'), get_full_path('libclang_rt.asan-preinit-x86_64.a'), 'x.a') assert 'E: lto-no-text-in-archive' not in output.print_results(output.results) def test_lto_archive_with_only_data(binariescheck): output, test = binariescheck run_elf_checks(test, FakePkg('fake'), get_full_path('only-data.a'), 'x.a') assert 'E: lto-no-text-in-archive' not in output.print_results(output.results) def test_archive_with_debuginfo(binariescheck): output, test = binariescheck run_elf_checks(test, FakePkg('fake'), get_full_path('archive-with-debuginfo.a'), 'x.a') assert 'E: static-library-without-debuginfo' not in output.print_results(output.results) @pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') def test_executable_stack(binariescheck): output, test = binariescheck run_elf_checks(test, FakePkg('fake'), get_full_path('executable-stack'), 'a.out') assert 'E: executable-stack' in output.results[0] def test_readelf_failure(): readelf = readelfparser('not-existing-file') assert 'No such file' in readelf.parsing_failed_reason() def test_readelf_failure_in_package(binariescheck): output, test = binariescheck run_elf_checks(test, FakePkg('fake'), get_full_path('not-existing.so'), '/lib64/not-existing.so') out = output.print_results(output.results) assert 'readelf-failed /lib64/not-existing.so' in out def test_readelf_single_error_message(binariescheck): output, test = binariescheck run_elf_checks(test, FakePkg('fake'), get_full_path('small_archive.a'), '/lib64/small_archive.a') out = output.print_results(output.results) filtered = [line for line in out.splitlines() if 'Not an ELF file' in line] assert len(filtered) == 1 @pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') def test_no_soname(binariescheck): output, test = binariescheck run_elf_checks(test, FakePkg('fake'), get_full_path('no-soname.so'), '/lib64/no-soname.so') out = output.print_results(output.results) assert 'no-soname /lib64/no-soname.so' in out @pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') def test_invalid_soname(binariescheck): output, test = binariescheck run_elf_checks(test, FakePkg('fake'), get_full_path('invalid-soname.so'), '/lib64/invalid-soname.so') out = output.print_results(output.results) assert 'invalid-soname /lib64/invalid-soname.so' in out assert 'E: shlib-with-non-pic-code /lib64/invalid-soname.so' not in out @pytest.mark.skipif(not IS_I686 and (not IS_X86_64 or not HAS_32BIT_GLIBC), reason='i686 glibc only') def test_non_pic_code_library(binariescheck): output, test = binariescheck run_elf_checks(test, FakePkg('fake'), get_full_path('non-pic-shared-m32.so'), '/usr/lib/non-pic-shared-m32.so') out = output.print_results(output.results) assert 'E: shlib-with-non-pic-code' in out @pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') def test_no_ldconfig_symlink(binariescheck): output, test = binariescheck run_elf_checks(test, FakePkg('libfake'), get_full_path('libutil-2.29.so'), '/lib64/libutil-2.29.so') out = output.print_results(output.results) assert 'no-ldconfig-symlink /lib64/libutil-2.29.so' in out assert 'E: shlib-policy-name-error SONAME: libutil.so.1, expected package suffix: 1' in out @pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') def test_call_mktemp(binariescheck): output, test = binariescheck run_elf_checks(test, FakePkg('fake'), get_full_path('call-mktemp'), '/bin/call-mktemp') out = output.print_results(output.results) assert 'E: call-to-mktemp /bin/call-mktemp' in out @pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') def test_call_setgroups(binariescheck): output, test = binariescheck run_elf_checks(test, FakePkg('fake'), get_full_path('call-setgroups'), '/bin/call-setgroups') out = output.print_results(output.results) assert 'E: missing-call-to-setgroups-before-setuid /bin/call-setgroups' in out @pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') def test_call_gethostbyname(binariescheck): output, test = binariescheck run_elf_checks(test, FakePkg('fake'), get_full_path('hostname'), '/usr/bin/hostname') out = output.print_results(output.results) assert 'W: binary-or-shlib-calls-gethostbyname' in out @pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') def test_missing_dependency(binariescheck): output, test = binariescheck run_elf_checks(test, FakePkg('fake'), get_full_path('no-dependency.so'), '/lib64/no-dependency.so') out = output.print_results(output.results) assert 'E: shared-library-without-dependency-information' in out def test_bca_files(binariescheck): output, test = binariescheck run_elf_checks(test, FakePkg('fake'), get_full_path('libkleeRuntimeFreeStanding.bca'), '/usr/lib64/klee/runtime/libkleeRuntimeFreeStanding.bca') out = output.print_results(output.results) assert 'E: ' not in out @pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') def test_shlib_policy_name_error(binariescheck): output, test = binariescheck run_elf_checks(test, FakePkg('libgame'), get_full_path('libgame.so'), '/lib64/libgame.so') out = output.print_results(output.results) assert 'libgame: E: shlib-policy-name-error SONAME: libgame2-1.9.so.10.0.0, expected package suffix: 1_9-10_0_0' in out rpmlint-2.2.0+ds1/test/test_shlib_policy.py000066400000000000000000000024511415540642600207440ustar00rootroot00000000000000import pytest from rpmlint.checks.SharedLibraryPolicyCheck import SharedLibraryPolicyCheck from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package @pytest.fixture(scope='function', autouse=True) def slpcheck(): CONFIG.info = True output = Filter(CONFIG) test = SharedLibraryPolicyCheck(CONFIG, output) return output, test @pytest.mark.parametrize('package', ['binary/libtest1']) def test_shlib_policy_wrong_name(tmpdir, package, slpcheck): output, test = slpcheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'W: shlib-unversioned-lib libtest.so.1x' in out @pytest.mark.parametrize('package', ['binary/libslp-missing-suffix']) def test_shlib_policy_missing_suffix(tmpdir, package, slpcheck): output, test = slpcheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: shlib-policy-excessive-dependency libsparta.so.2' in out @pytest.mark.parametrize('package', ['binary/libslp1234']) def test_shlib_policy_errors(tmpdir, package, slpcheck): output, test = slpcheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'W: shlib-fixed-dependency libsparta.so.2 = 1.23' in out rpmlint-2.2.0+ds1/test/test_signature.py000066400000000000000000000036531415540642600202720ustar00rootroot00000000000000import pytest from rpmlint.checks.SignatureCheck import SignatureCheck from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package @pytest.fixture(scope='function', autouse=True) def signaturecheck(): CONFIG.info = True output = Filter(CONFIG) test = SignatureCheck(CONFIG, output) return output, test # The signature was stripped via "rpmsign --delsign " @pytest.mark.parametrize('package', ['binary/no-signature']) def test_no_signature(tmpdir, package, signaturecheck): output, test = signaturecheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: no-signature' in out assert 'E: unknown-key' not in out assert 'E: invalid-signature' not in out # The test rpm was signed with gpg key created for this purpose that is not # imported in rpm db and therefore unknown-key error should be thrown @pytest.mark.parametrize('package', ['binary/unknown-key']) def test_unknown_key(tmpdir, package, signaturecheck): output, test = signaturecheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: unknown-key 31fdc502' in out assert 'E: no-signature' not in out assert 'E: invalid-signature' not in out # The test rpm hello-2.0-1.x86_64-signed.rpm was taken from # https://github.com/rpm-software-management/rpm/blob/master/tests/data/RPMS/ # and then the signature was corrupted by running "dd if=/dev/zero # of=hello-2.0-1.x86_64-signed.rpm conv=notrunc bs=1 seek=264 count=6 # 2> /dev/null" @pytest.mark.parametrize('package', ['binary/hello']) def test_invalid_signature(tmpdir, package, signaturecheck): output, test = signaturecheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: invalid-signature' in out assert 'E: no-signature' not in out assert 'E: unknown-key' not in out rpmlint-2.2.0+ds1/test/test_sources.py000066400000000000000000000024601415540642600177470ustar00rootroot00000000000000import pytest from rpmlint.checks.SourceCheck import SourceCheck from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package @pytest.fixture(scope='function', autouse=True) def sourcescheck(): CONFIG.info = True output = Filter(CONFIG) test = SourceCheck(CONFIG, output) return output, test @pytest.mark.parametrize('package', ['source/wrongsrc']) def test_extension_and_permissions(tmpdir, package, sourcescheck): output, test = sourcescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert len(output.results) == 1 assert 'inconsistent-file-extension' in out assert 'name extension indicates a different compression format' in out assert 'strange-permission' not in out assert 'a file should have' not in out @pytest.mark.parametrize('package', ['source/not-compressed-multi-spec']) def test_compression_and_multispec(tmpdir, package, sourcescheck): output, test = sourcescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'source-not-compressed' in out assert 'source archive or file in your package is not compressed' in out assert 'multiple-specfiles' in out assert 'package contains multiple spec files' in out rpmlint-2.2.0+ds1/test/test_speccheck.py000066400000000000000000001201221415540642600202100ustar00rootroot00000000000000import re import pytest from rpmlint.checks.SpecCheck import SpecCheck from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package, get_tested_spec_package @pytest.fixture(scope='function', autouse=True) def speccheck(): CONFIG.info = True output = Filter(CONFIG) test = SpecCheck(CONFIG, output) return output, test def test_check_include(tmpdir, speccheck): output, test = speccheck test.check_source(get_tested_package('source/CheckInclude', tmpdir)) out = output.print_results(output.results) assert "specfile-error can't parse specfile" not in out assert 'no-buildroot-tag' in out assert 'E: specfile-error error: query of specfile' not in out @pytest.mark.parametrize('package', ['spec/SpecCheck2', 'spec/SpecCheck3']) def test_patch_not_applied(package, speccheck): output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'patch-not-applied' not in out @pytest.mark.parametrize('package', ['spec/SpecCheck']) def test_distribution_tags(package, speccheck): output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'patch-not-applied Patch3' in out assert not re.search(r'patch-not-applied Patch\b', out) assert not re.search('patch-not-applied Patch[0124567]', out) assert 'libdir-macro-in-noarch-package' not in out assert len(re.findall('macro-in-comment', out)) == 1 assert 'unversioned-explicit-provides unversioned-provides' in out assert 'unversioned-explicit-provides versioned-provides' not in out assert 'unversioned-explicit-provides /' not in out assert 'unversioned-explicit-obsoletes unversioned-obsoletes' in out assert 'unversioned-explicit-obsoletes versioned-obsoletes' not in out assert 'unversioned-explicit-obsoletes /' not in out assert 'setup-not-quiet' in out @pytest.mark.parametrize('package', ['spec/SpecCheck4']) def test_forbidden_controlchars_found(package, speccheck): output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'E: forbidden-controlchar-found Requires:' in out assert 'E: forbidden-controlchar-found Provides:' in out assert 'E: forbidden-controlchar-found Obsoletes:' in out assert 'E: forbidden-controlchar-found Conflicts:' in out assert 'E: forbidden-controlchar-found %changelog:' in out @pytest.mark.parametrize('package', ['source/no-spec-file']) def test_check_no_spec_file(tmpdir, package, speccheck): """Test if spec file is not found inside RPM metadata.""" output, test = speccheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: no-spec-file' in out @pytest.mark.parametrize('package', ['source/CheckInclude']) def test_check_no_spec_file_not_applied(tmpdir, package, speccheck): """Test if there is no spec file inside RPM metadata.""" output, test = speccheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: no-spec-file' not in out @pytest.mark.parametrize('package', ['spec/non-utf8-spec-file']) def test_check_non_utf8_spec_file(package, speccheck): """Test if specfile does not have UTF-8 character encoding.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'E: non-utf8-spec-file' in out @pytest.mark.parametrize('package', ['spec/SpecCheckTemp']) def test_check_non_utf8_spec_file_not_applied(package, speccheck): """Test if specfile has UTF-8 character encoding.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'E: non-utf8-spec-file' not in out @pytest.mark.parametrize('package', ['source/invalid-spec-name']) def test_check_invalid_spec_name(tmpdir, package, speccheck): """Test if specfile name does not matches the ('Name: ') tag.""" output, test = speccheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: invalid-spec-name' in out @pytest.mark.parametrize('package', ['source/CheckInclude']) def test_check_invalid_spec_name_not_applied(tmpdir, package, speccheck): """Test if specfile has specfile name as ('Name: ') tag.""" output, test = speccheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: invalid-spec-name' not in out @pytest.mark.parametrize('package', ['spec/non-break-space']) def test_check_non_break_space(package, speccheck): """Test if specfile has a nbsp character.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: non-break-space' in out @pytest.mark.parametrize('package', ['spec/SpecCheck2']) def test_check_non_break_space_not_applied(package, speccheck): """Test if specfile does not have any nbsp character.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: non-break-space' not in out @pytest.mark.parametrize('package', ['spec/rpm-buildroot-usage']) def test_check_rpm_buildroot_usage_under_prep(package, speccheck): """Test if specfile has buildroot macro under %prep.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'E: rpm-buildroot-usage' in out @pytest.mark.parametrize('package', ['spec/SpecCheckTemp']) def test_check_rpm_buildroot_usage_under_build(package, speccheck): """Test if specfile has buildroot macro under %build.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'E: rpm-buildroot-usage' in out @pytest.mark.parametrize('package', ['spec/SpecCheck2']) def test_check_rpm_buildroot_usage_not_applied(package, speccheck): """Test if specfile does not have buildroot macro inside specfile.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'E: rpm-buildroot-usage' not in out @pytest.mark.parametrize('package', ['spec/make-check-outside-check-section']) def test_check_make_check_outside_check_section(package, speccheck): """Test if specfile has `make check` outside %check.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: make-check-outside-check-section' in out @pytest.mark.parametrize('package', ['spec/SpecCheckTemp']) def test_check_make_check_inside_check_section(package, speccheck): """Test if specfile has `make check` inside all the required ('check', 'changelog', 'package', 'description') section. """ output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: make-check-outside-check-section' not in out @pytest.mark.parametrize('package', ['spec/SpecCheck2']) def test_check_make_check_outside_not_applied(package, speccheck): """Test if specfile does not have any `make check`.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: make-check-outside-check-section' not in out @pytest.mark.parametrize('package', ['spec/setup-not-quiet']) def test_check_setup_not_quiet(package, speccheck): """Test if specfile does not have %setup -q macro.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: setup-not-quiet' in out @pytest.mark.parametrize('package', ['spec/SpecCheckTemp']) def test_check_setup_is_quiet(package, speccheck): """Test if specfile has a %setup -q macro.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: setup-not-quiet' not in out @pytest.mark.parametrize('package', ['spec/SpecCheck2']) def test_check_setup_not_quiet_not_applied(package, speccheck): """Test if specfile does not have a setup macro.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: setup-not-quiet' not in out @pytest.mark.parametrize('package', ['spec/setup-not-in-prep']) def test_check_setup_not_in_prep(package, speccheck): """Test if specfile does not have %setup inside %prep.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: setup-not-in-prep' in out @pytest.mark.parametrize('package', ['spec/SpecCheckTemp']) def test_check_setup_inside_prep(package, speccheck): """Test if specfile has %setup inside %prep.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: setup-not-in-prep' not in out @pytest.mark.parametrize('package', ['spec/SpecCheck2']) def test_check_setup_not_in_prep_not_applied(package, speccheck): """Test if specfile has no %setup.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: setup-not-in-prep' not in out @pytest.mark.parametrize('package', ['spec/%autopatch-not-in-prep']) def test_check_autopatch_not_in_prep(package, speccheck): """Test if specfile does not have %autopatch inside %prep.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: %autopatch-not-in-prep' in out @pytest.mark.parametrize('package', ['spec/SpecCheckTemp']) def test_check_autopatch_in_prep(package, speccheck): """Test if specfile has %autopatch inside %prep.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: %autopatch-not-in-prep' not in out @pytest.mark.parametrize('package', ['spec/SpecCheck2']) def test_check_autopatch_not_in_prep_not_applied(package, speccheck): """Test if specfile has no %autopatch.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: %autopatch-not-in-prep' not in out @pytest.mark.parametrize('package', ['spec/%autosetup-not-in-prep']) def test_check_autosetup_not_in_prep(package, speccheck): """Test if specfile does not have %autosetup inside %prep.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: %autosetup-not-in-prep' in out @pytest.mark.parametrize('package', ['spec/SpecCheckTemp']) def test_check_autosetup_inside_prep(package, speccheck): """Test if specfile has %autosetup in %prep.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: %autosetup-not-in-prep' not in out @pytest.mark.parametrize('package', ['spec/SpecCheck2']) def test_check_autosetup_not_in_prep_not_applied(package, speccheck): """Test if specfile has no %autosetup.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: %autosetup-not-in-prep' not in out @pytest.mark.parametrize('package', ['spec/use-of-RPM-SOURCE-DIR']) def test_check_use_of_rpm_source_dir(package, speccheck): """Test if specfile consist of $RPM_SOURCE_DIR or %{_sourcedir}.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'E: use-of-RPM_SOURCE_DIR' in out @pytest.mark.parametrize('package', ['spec/SpecCheck2']) def test_check_use_of_rsd_not_applied(package, speccheck): """rsd: RPM_SOURCE_DIR Test if specfile does not consist of $RPM_SOURCE_DIR or %{_sourcedir}. """ output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'E: use-of-RPM_SOURCE_DIR' not in out @pytest.mark.parametrize('package', ['spec/configure-without-libdir-spec']) def test_check_configure_without_libdir_spec(package, speccheck): """Test if specfile does not have options augmented with --libdir.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: configure-without-libdir-spec' in out @pytest.mark.parametrize('package', ['spec/SpecCheck2']) def test_check_cwls_not_applied(package, speccheck): """cwls: configure-without-libdir-spec Test if specfile has options augmented with --libdir. """ output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: configure-without-libdir-spec' not in out @pytest.mark.parametrize('package', ['spec/hardcoded-library-path']) def test_check_hardcoded_library_path(package, speccheck): """Test if specfile has hardcoded library path.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'E: hardcoded-library-path' in out @pytest.mark.parametrize('package', ['spec/SpecCheck2']) def test_check_hclp_not_applied(package, speccheck): """hclp: hardcoded library path Test if specfile does not have hardcoded library path. """ output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'E: hardcoded-library-path' not in out @pytest.mark.parametrize('package', ['spec/obsolete-tag']) def test_check_obsolete_tag(package, speccheck): """Test if specfile has obsolete-tag as Copyright or Serial.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: obsolete-tag 2' in out assert 'W: obsolete-tag Something' in out @pytest.mark.parametrize('package', ['spec/SpecCheck2']) def test_check_obsolete_tag_not_applied(package, speccheck): """Test if specfile does not have obsolete-tag as Copyright or Serial.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: obsolete-tag' not in out @pytest.mark.parametrize('package', ['spec/hardcoded-path-in-buildroot-tag']) def test_check_hardcoded_path_in_buildroot_tag(package, speccheck): """Test if specfile has hardoded path in buildroot tag.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: hardcoded-path-in-buildroot-tag /usr/bin/bash' in out @pytest.mark.parametrize('package', ['spec/SpecCheck2']) def test_check_hpbt_not_applied(package, speccheck): """hpbt: hardcoded-path-in-buildroot-tag Test if specfile does not have hardoded path in buildroot tag. """ output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: hardcoded-path-in-buildroot-tag %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)' not in out @pytest.mark.parametrize('package', ['spec/buildarch-instead-of-exclusivearch-tag']) def test_check_buildarch_instead_of_exclusivearch_tag(package, speccheck): """Test if specfile has BuildArch has any architecture beside noarch.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'E: buildarch-instead-of-exclusivearch-tag x86_64' in out assert 'E: buildarch-instead-of-exclusivearch-tag i586' in out @pytest.mark.parametrize('package', ['spec/SpecCheck2']) def test_check_biet_not_applied(package, speccheck): """biet: buildarch-instead-of-exclusivearch-tag Test if specfile has BuildArch with noarch. """ output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'E: buildarch-instead-of-exclusivearch-tag noarch' not in out @pytest.mark.parametrize('package', ['spec/hardcoded-packager-tag']) def test_check_hardcoded_packager_tag(package, speccheck): """Test if specfile has hardcoded packager tag.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: hardcoded-packager-tag Someone' in out @pytest.mark.parametrize('package', ['spec/SpecCheck2']) def test_check_hardcoded_packager_tag_not_applied(package, speccheck): """Test if specfile does not have hardcoded packager tag.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: hardcoded-packager-tag' not in out @pytest.mark.parametrize('package', ['spec/hardcoded-prefix-tag']) def test_check_hardcoded_prefix_tag(package, speccheck): """Test if specfile has hardcoded prefix tag.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: hardcoded-prefix-tag' in out @pytest.mark.parametrize('package', ['spec/SpecCheck2']) def test_check_hardcoded_prefix_tag_not_applied(package, speccheck): """Test if specfile does not have hardcoded prefix tag.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: hardcoded-prefix-tag' not in out @pytest.mark.parametrize('package', ['spec/prereq_use']) def test_check_prereq_use(package, speccheck): """Test if specfile has tags such as PreReq(pre) or PreReq(post). """ output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'E: prereq-use none' in out assert 'E: prereq-use none_other' in out @pytest.mark.parametrize('package', ['spec/patch-not-applied']) def test_check_prereq_use_not_found(package, speccheck): """Test if specfile has no PreReq tag value.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'E: prereq-use' not in out @pytest.mark.parametrize('package', ['spec/mixed-use-of-spaces-and-tabs']) def test_check_prereq_use_not_applied(package, speccheck): """Test if specfile has no PreReq tag value.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'E: prereq-use' not in out @pytest.mark.parametrize('package', ['spec/buildprereq-use']) def test_check_buildprereq_use(package, speccheck): """Test if specfile has buildprereq tag.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'E: buildprereq-use Something' in out @pytest.mark.parametrize('package', ['spec/SpecCheck2']) def test_check_buildprereq_use_not_applied(package, speccheck): """Test if specfile does not have buildprereq tag.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'E: buildprereq-use' not in out @pytest.mark.parametrize('package', ['spec/forbidden-controlchar-found']) def test_check_forbidden_controlchar_found(package, speccheck): """Test if specfile has forbidden controlchar in various parts of specfile. """ output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'E: forbidden-controlchar-found' in out @pytest.mark.parametrize('package', ['spec/SpecCheckTemp']) def test_check_forbidden_controlchar_not_found(package, speccheck): """Test if specfile does not have forbidden controlchar in various parts of specfile. """ output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'E: forbidden-controlchar-found' not in out @pytest.mark.parametrize('package', ['spec/comparison-operator-in-deptoken']) def test_check_coid(package, speccheck): """coid: comparison-operator-in-deptoken Test if specfile has comparison operator(>, =) in deptoken. """ output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: comparison-operator-in-deptoken something>2.0' in out assert 'W: comparison-operator-in-deptoken Something>1.0' in out assert 'W: comparison-operator-in-deptoken Something=2.0' in out @pytest.mark.parametrize('package', ['spec/SpecCheckTemp']) def test_check_coid_found(package, speccheck): """coid: comparison-operator-in-deptoken Test if specfile has comparison operator(<, <=) in deptoken. """ output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: comparison-operator-in-deptoken Someotherthing<1.0' in out assert 'W: comparison-operator-in-deptoken Someotherthing<=2.0' in out @pytest.mark.parametrize('package', ['spec/SpecCheck2']) def test_check_coid_is_found(package, speccheck): """coid: comparison-operator-in-deptoken Test if specfile has comparison operator(==, >=) in deptoken. """ output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: comparison-operator-in-deptoken Oneanotherthing>=1.0' in out assert 'W: comparison-operator-in-deptoken Onelastthing==2.0' in out assert 'W: comparison-operator-in-deptoken source-for-second-rpm' not in out @pytest.mark.parametrize('package', ['spec/%autopatch-not-in-prep']) def test_check_coid_is_found_with_single_space(package, speccheck): """coid: comparison-operator-in-deptoken Test if specfile has comparison operator(>=, <=) with single space in deptoken. """ output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: comparison-operator-in-deptoken Someotherthinwithsinglespace<=' in out @pytest.mark.parametrize('package', ['spec/%autosetup-not-in-prep']) def test_check_coid_is_found_with_double_space(package, speccheck): """coid: comparison-operator-in-deptoken Test if specfile has comparison operator(>=, <=) with single space in deptoken. """ output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: comparison-operator-in-deptoken /Something' not in out @pytest.mark.parametrize('package', ['spec/unversioned-explicit-version']) def test_check_unversioned_explicit_version(package, speccheck): """Test if specfile has Provides: tag.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: unversioned-explicit-provides someones-something=%{version}' in out @pytest.mark.parametrize('package', ['spec/%autosetup-not-in-prep']) def test_check_unversioned_explicit_version_not_found(package, speccheck): """Test if specfile has Provides: /something tag.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: unversioned-explicit-provides /something' not in out @pytest.mark.parametrize('package', ['spec/SpecCheck2']) def test_check_uev_not_applied(package, speccheck): """uev: unversioned-explicit-version 1. Test if specfile does not have Provides: tag. 2. Test if specfile does not have Obsoletes: tag. """ output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: unversioned-explicit-provides' not in out @pytest.mark.parametrize('package', ['spec/unversioned-explicit-obsoletes']) def test_check_unversioned_explicit_obsoletes(package, speccheck): """Test if specfile has Obsoletes: tag.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: unversioned-explicit-obsoletes Something' in out @pytest.mark.parametrize('package', ['spec/%autopatch-not-in-prep']) def test_check_unversioned_explicit_obsoletes_not_found(package, speccheck): """Test if specfile has correct Obsoletes: tag and Provides: tag in specfile. """ output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: unversioned-explicit-obsoletes %{name} <= %{version}' not in out assert 'W: unversioned-explicit-obsoletes %{name} = %{version}' not in out @pytest.mark.parametrize('package', ['spec/SpecCheckTemp']) def test_check_ueo_not_found(package, speccheck): """ueo: unversioned-explicit-obsoletes Test if specfile does has Obsoletes: /something. """ output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: unversioned-explicit-obsoletes /something' not in out @pytest.mark.parametrize('package', ['spec/macro-in-changelog']) def test_check_macro_in_changelog(package, speccheck): """Test if specfile has macro in %changelog.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: macro-in-%changelog' in out @pytest.mark.parametrize('package', ['spec/macro-in-changelog-autochangelog']) def test_check_autochangelog(package, speccheck): """Test usage of %autochangelog macro.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: macro-in-%changelog' not in out @pytest.mark.parametrize('package', ['spec/SpecCheck2']) def test_check_macro_in_changelog_not_found(package, speccheck): """Test if specfile has macro in %changelog consisting of %%foo or %+foo or %.foo. """ output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: macro-in-%changelog' not in out @pytest.mark.parametrize('package', ['spec/SpecCheckTemp']) def test_check_macro_in_changelog_not_applied(package, speccheck): """Test if specfile does not have macro in %changelog.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: macro-in-%changelog' not in out @pytest.mark.parametrize('package', ['spec/libdir-macro-in-noarch-package']) def test_check_libdir_macro_in_noarch_package(package, speccheck): """Test if specfile has _libdir macro in noarch package.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: libdir-macro-in-noarch-package' in out @pytest.mark.parametrize('package', ['spec/mixed-use-of-spaces-and-tabs']) def test_check_lmnp_not_applied(package, speccheck): """lmnp: libdir-macro-in-noarch-package Test if specfile does not have _libdir macro in noarch package. """ output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: libdir-macro-in-noarch-package' not in out @pytest.mark.parametrize('package', ['spec/deprecated-grep']) def test_check_deprecated_grep(package, speccheck): """Test if specfile has direct use of grep or egrep or fgrep.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: deprecated-grep' in out @pytest.mark.parametrize('package', ['spec/SpecCheckTemp']) def test_check_deprecated_grep_not_found(package, speccheck): """Test if specfile has grep with -F or -E inside package, changelog, depscription, files macro """ output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: deprecated-grep' not in out @pytest.mark.parametrize('package', ['spec/SpecCheck2']) def test_check_deprecated_grep_not_applied(package, speccheck): """Test if specfile has no use of egrep/fgrep or egrep/fgrep with -E or -F.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: deprecated-grep' not in out # TODO: Add test for non-standard-Group @pytest.mark.parametrize('package', ['spec/macro-in-comment']) def test_check_macro_in_comment(package, speccheck): """Test if specfile has macro in comment.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: macro-in-comment' in out @pytest.mark.parametrize('package', ['spec/%autosetup-not-in-prep']) def test_check_macro_in_comment_not_found(package, speccheck): """Test if specfile has comment in macro.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: macro-in-comment' not in out @pytest.mark.parametrize('package', ['spec/SpecCheck2']) def test_check_macro_in_comment_not_applied(package, speccheck): """Test if specfile does not have macro inside a comment.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: macro-in-comment' not in out @pytest.mark.parametrize('package', ['spec/no-buildroot-tag']) def test_check_no_build_root_tag(package, speccheck): """Test if specfile does not have BuildRoot tag.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: no-buildroot-tag' in out @pytest.mark.parametrize('package', ['spec/SpecCheck2']) def test_check_no_build_root_tag_not_applied(package, speccheck): """Test if specfile has BuildRoot tag.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: no-buildroot-tag' not in out @pytest.mark.parametrize('package', ['spec/no-%%%s-section']) def test_check_no_essential_section(package, speccheck): """Test for no-%%%s-section check Test if specfile does not have essential section tag. """ output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: no-%prep-section' in out assert 'W: no-%install-section' in out assert 'W: no-%build-section' in out @pytest.mark.parametrize('package', ['spec/SpecCheck2']) def test_check_no_essential_section_not_applied(package, speccheck): """Test for no-%%%s-section check Test if specfile has all essential section tag. """ output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: no-%prep-section' not in out assert 'W: no-%install-section' not in out assert 'E: superfluous-%clean-section' in out assert 'W: no-%build-section' not in out @pytest.mark.parametrize('package', ['spec/more-than-one-%changelog']) def test_check_more_than_one_changelog_section(package, speccheck): """Test if specfile has more than one changelog section.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: more-than-one-%changelog-section' in out @pytest.mark.parametrize('package', ['spec/SpecCheck2']) def test_check_mtocs_not_applied(package, speccheck): """mtocs: more-than-one-%changelog-section Test if specfile does not have more than one changelog section. """ output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: more-than-one-%changelog-section' not in out @pytest.mark.parametrize('package', ['spec/lib-package-without-%mklibname']) def test_check_lib_package_without_mklibname(package, speccheck): """Test if specfile has lib pacakge without %mklibname.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'E: lib-package-without-%mklibname' in out @pytest.mark.parametrize('package', ['spec/SpecCheck2']) def test_check_lpwm_not_applied(package, speccheck): """lpwm: lib-package-without-%mklibname Test if specfile does not have lib pacakge without %mklibname.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'E: lib-package-without-%mklibname' not in out @pytest.mark.parametrize('package', ['spec/depscript-without-disabling-depgen']) def test_check_depscript_without_disabling_depgen(package, speccheck): """Test if specfile has define __find_provides/requires.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: depscript-without-disabling-depgen' in out @pytest.mark.parametrize('package', ['spec/SpecCheckTemp']) def test_check_depscript_without_disabling_depgen_not_found(package, speccheck): """Test if specfile has define __find_provides/requires with %define _use_internal_dependency_generator set to 0. """ output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: depscript-without-disabling-depgen' not in out @pytest.mark.parametrize('package', ['spec/SpecCheck2']) def test_check_dwdd_not_applied(package, speccheck): """dwdd: depscript-without-disabling-depgen Test if specfile does not have define __find_provides/requires.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: depscript-without-disabling-depgen' not in out @pytest.mark.parametrize('package', ['spec/patch-fuzz-is-changed']) def test_check_patch_fuzz_is_changed(package, speccheck): """Test if specfile has internal/default patch fuzz value changed as %define _default_patch_fuzz >= 0. """ output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: patch-fuzz-is-changed' in out @pytest.mark.parametrize('package', ['spec/SpecCheckTemp']) def test_check_patch_fuzz_is_changed_not_found(package, speccheck): output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: patch-fuzz-is-changed' not in out @pytest.mark.parametrize('package', ['spec/macro-in-comment']) def test_check_patch_fuzz_is_changed_not_applied(package, speccheck): output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: patch-fuzz-is-changed' not in out @pytest.mark.parametrize('package', ['spec/mixed-use-of-spaces-and-tabs']) def test_check_mixed_use_of_spaces_and_tabs(package, speccheck): """Test if specfile has mix use of space and tabs.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: mixed-use-of-spaces-and-tabs' in out @pytest.mark.parametrize('package', ['spec/SpecCheck2']) def test_check_mixed_use_of_spaces_and_tabs_not_applied(package, speccheck): """Test if specfile does not have mix use of space and tabs.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: mixed-use-of-spaces-and-tabs' not in out @pytest.mark.parametrize('package', ['spec/%ifarch-applied-patch']) def test_check_ifarch_applied_patch(package, speccheck): """Test if specfile has no patch in %ifarch block.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: %ifarch-applied-patch' in out @pytest.mark.parametrize('package', ['spec/mixed-use-of-spaces-and-tabs']) def test_check_ifarch_applied_not_enforced(package, speccheck): """Test if specfile has patch in %ifarch block.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: %ifarch-applied-patch' not in out @pytest.mark.parametrize('package', ['spec/patch-not-applied']) def test_check_patch_not_applied(package, speccheck): """Test if specfile does not have all patch applied.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: patch-not-applied' in out @pytest.mark.parametrize('package', ['spec/mixed-use-of-spaces-and-tabs']) def test_check_patch_not_found(package, speccheck): """Test if specfile have all patch applied by %autopatch.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: patch-not-applied' not in out @pytest.mark.parametrize('package', ['spec/mixed-use-of-spaces-and-tabs']) def test_check_patch_not_applied_not_enforced(package, speccheck): """Test if specfile has all patch applied.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: patch-not-applied' not in out # TODO: Add specfile-error test. @pytest.mark.parametrize('package', ['spec/SpecCheck2']) def test_check_invalid_url(package, speccheck): """Test if specfile has invalid url.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: invalid-url' in out @pytest.mark.parametrize('package', ['spec/mixed-use-of-spaces-and-tabs']) def test_check_invalid_url_not_applied(package, speccheck): """Test if specfile does not have invalid url.""" output, test = speccheck pkg = get_tested_spec_package(package) test.check_spec(pkg) out = output.print_results(output.results) assert 'W: invalid-url' not in out rpmlint-2.2.0+ds1/test/test_spellchecking.py000066400000000000000000000056001415540642600210760ustar00rootroot00000000000000import pytest import rpmlint.spellcheck from Testing import HAS_CZECH_DICTIONARY, HAS_ENGLISH_DICTIONARY @pytest.mark.skipif(not rpmlint.spellcheck.ENCHANT, reason='Missing enchant bindings') @pytest.mark.skipif(not HAS_ENGLISH_DICTIONARY, reason='Missing English dictionary') def test_spelldict(capsys): """ Check we can init dictionary spellchecker """ spell = rpmlint.spellcheck.Spellcheck() spell._init_checker() out, err = capsys.readouterr() assert not out assert not err assert 'unable to load spellchecking dictionary' not in err spell._init_checker('not-existing-language') out, err = capsys.readouterr() assert not out assert 'unable to load spellchecking dictionary' in err assert 'en_US' in spell._enchant_checkers assert spell._enchant_checkers['en_US'] is not None assert 'not-existing-language' not in spell._enchant_checkers @pytest.mark.skipif(not rpmlint.spellcheck.ENCHANT, reason='Missing enchant bindings') @pytest.mark.skipif(not HAS_ENGLISH_DICTIONARY, reason='Missing English dictionary') @pytest.mark.skipif(not HAS_CZECH_DICTIONARY, reason='Missing Czech dictionary') def test_spellchecking(): """ Check if we can test the spelling """ spell = rpmlint.spellcheck.Spellcheck() # correct text text = 'I swear this text is proper English' result = spell.spell_check(text, 'Description({}):') assert not result # english 2 typos text = "I don't think tihs tetx is correct English" result = spell.spell_check(text, 'Description({}):') assert len(result) == 2 assert result['tihs'] == 'Description(en_US): tihs -> this, hits, ties' # different language, one typo text = 'Příčerně žluťoučký kůň' result = spell.spell_check(text, 'Summary({}):', 'cs_CZ') assert len(result) == 1 assert result['Příčerně'] == 'Summary(cs_CZ): Příčerně -> Příčetně, Příčeně, Příšerně' # non-existing language, should return nothing: text = 'Weird english text' result = spell.spell_check(text, 'Summary({}):', 'de_CZ') assert not result @pytest.mark.skipif(not rpmlint.spellcheck.ENCHANT, reason='Missing enchant bindings') @pytest.mark.skipif(not HAS_ENGLISH_DICTIONARY, reason='Missing English dictionary') def test_pkgname_spellchecking(): spell = rpmlint.spellcheck.Spellcheck() pkgname = 'python-squeqe' text = "This package is squeqe's framework helper" result = spell.spell_check(text, 'Description({}):', 'en_US', pkgname) assert not result @pytest.mark.skipif(not rpmlint.spellcheck.ENCHANT, reason='Missing enchant bindings') def test_ignorelist_spellchecking(): spell = rpmlint.spellcheck.Spellcheck() ignore = ['wrod', 'žížala'] text = 'This package should not have any typos in wrod or žíŽala' result = spell.spell_check(text, 'Description({}):', ignored_words=ignore) assert not result rpmlint-2.2.0+ds1/test/test_sysvinitonsystemd.py000066400000000000000000000020771415540642600221260ustar00rootroot00000000000000import pytest from rpmlint.checks.SysVInitOnSystemdCheck import SysVInitOnSystemdCheck from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package @pytest.fixture(scope='function', autouse=True) def sysvcheck(): CONFIG.info = True output = Filter(CONFIG) test = SysVInitOnSystemdCheck(CONFIG, output) return output, test @pytest.mark.parametrize('package', ['binary/init']) def test_sysv_init_on_systemd_check(tmpdir, package, sysvcheck): output, test = sysvcheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: obsolete-insserv-requirement' in out assert 'E: deprecated-init-script weekly.script' in out assert 'E: deprecated-boot-script boot.script' in out @pytest.mark.parametrize('package', ['binary/rc-links']) def test_overshadowing_of_initscript(tmpdir, package, sysvcheck): output, test = sysvcheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: systemd-shadowed-initscript bar' in out rpmlint-2.2.0+ds1/test/test_tags.py000066400000000000000000000410271415540642600172240ustar00rootroot00000000000000import pytest from rpmlint.checks.TagsCheck import TagsCheck from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package @pytest.fixture(scope='function', autouse=True) def tagscheck(): CONFIG.info = True output = Filter(CONFIG) test = TagsCheck(CONFIG, output) return output, test @pytest.mark.parametrize('package', ['binary/unexpanded1']) def test_unexpanded_macros(tmpdir, package, tagscheck): output, test = tagscheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'unexpanded-macro Recommends' in out assert 'unexpanded-macro Provides' in out assert 'unexpanded-macro Conflicts' in out assert 'unexpanded-macro Suggests' in out assert 'unexpanded-macro Obsoletes' in out assert 'unexpanded-macro Enhances' in out @pytest.mark.parametrize('package', ['binary/self']) def test_self_provides(tmpdir, package, tagscheck): output, test = tagscheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: useless-provides self' in out @pytest.mark.parametrize('package', ['binary/foo-devel']) def test_development_package(tmpdir, package, tagscheck): output, test = tagscheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'W: devel-package-with-non-devel-group Games' in out @pytest.mark.parametrize('package', ['binary/missingprovides']) def test_missing_provides(tmpdir, package, tagscheck): output, test = tagscheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: no-pkg-config-provides' in out @pytest.mark.parametrize('package', ['binary/invalid-exception']) def test_invalid_license_exception(tmpdir, package, tagscheck): output, test = tagscheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'W: invalid-license-exception sparta' in out @pytest.mark.parametrize('package', ['binary/valid-exception']) def test_valid_license_exception(tmpdir, package, tagscheck): CONFIG.info = True CONFIG.configuration['ValidLicenseExceptions'] = ['389-exception'] output = Filter(CONFIG) test = TagsCheck(CONFIG, output) test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'W: invalid-license-exception' not in out @pytest.mark.parametrize('package', ['binary/xtables-addons-kmp-default']) def test_forbidden_controlchar_found_requires(tmpdir, package, tagscheck): output, test = tagscheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: forbidden-controlchar-found Requires:' in out @pytest.mark.parametrize('package', ['binary/ruby2.6-rubygem-fast_gettext']) def test_forbidden_controlchar_found_changelog(tmpdir, package, tagscheck): output, test = tagscheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: forbidden-controlchar-found %changelog' in out @pytest.mark.parametrize('package', ['binary/SpecCheck4']) def test_forbidden_controlchar_found(tmpdir, package, tagscheck): output, test = tagscheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: forbidden-controlchar-found Requires:' in out assert 'E: forbidden-controlchar-found Provides:' in out assert 'E: forbidden-controlchar-found Obsoletes:' in out assert 'E: forbidden-controlchar-found Conflicts:' in out assert 'E: forbidden-controlchar-found %changelog :' in out @pytest.mark.parametrize('package', ['binary/unexpanded-macro-exp']) def test_check_unexpanded_macro(tmpdir, package, tagscheck): """Test if a package has an unexpanded macro in it's specfile.""" output, test = tagscheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'W: unexpanded-macro Packager %ppc' in out assert 'W: unexpanded-macro Group %ppc' in out assert 'W: unexpanded-macro Provides /something%ppc %ppc' in out assert 'W: unexpanded-macro Conflicts something:%ppc %ppc' in out assert 'W: unexpanded-macro Supplements packageand(python-gobject:%{gdk_real_package})%ppc %ppc' in out assert 'W: unexpanded-macro Suggests /%ppc %ppc' in out assert 'W: unexpanded-macro Enhances /%ppc %ppc' in out assert 'W: unexpanded-macro Recommends /%ppc %ppc' in out assert 'W: unexpanded-macro Supplements packageand(python-gobject:%{gdk_real_package})%ppc %{gdk_real_package}' in out assert 'W: unexpanded-macro Supplements packageand(python-gobject:%{gdk_real_package})%ppc %ppc' in out @pytest.mark.parametrize('package', ['binary/invalid-version']) def test_check_errors(tmpdir, package, tagscheck): """Test package for check invalid-version.""" output, test = tagscheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) # Test if a package has a Version: tag with pre/alpha/beta suffixes in it's specfile assert 'E: invalid-version 0pre' in out # TODO: Add a test for no-changelogname-tag not in out @pytest.mark.parametrize('package', ['binary/summary-warning']) def test_check_summary_warning(tmpdir, package, tagscheck): """Test package for check - in out, summary-too-long, summary-has-leading-spaces, description-shorter-than-summary, - not in out invalid-version, unexpanded-macro. """ output, test = tagscheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) # Test if package has a summary longer than 80 characters assert 'E: summary-too-long   lorem Ipsum is simply dummy text of the printing and typesetting industry' in out # Test if package has leading space at the beginning of the summary assert 'E: summary-has-leading-spaces   lorem Ipsum is simply dummy text of the printing and typesetting industry' in out # Test if package has a shorter description than Summary assert 'W: description-shorter-than-summary' in out # Test if a package has a Version: tag # without any pre/alpha/beta suffixes in it's specfile. assert 'E: invalid-version' not in out # Test if a package does not have an unexpanded # macro in it's specfile. assert 'W: unexpanded-macro' not in out assert 'E: no-group-tag' in out @pytest.mark.parametrize('package', ['binary/no-url-tag']) def test_check_warning(tmpdir, package, tagscheck): """Test if a package contains the warning for summary-not-capitalized, summary-ended-with-dot, no-url-tag.""" output, test = tagscheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) # Test if package Summary does not start with a capital letter assert 'W: summary-not-capitalized no-url-tag warning.' in out # Test if a package Summary ends with a period assert 'W: summary-ended-with-dot no-url-tag warning.' in out # Test if a package does not contain a Url: tag assert 'W: no-url-tag' in out @pytest.mark.parametrize('package', ['binary/invalid-la-file']) def test_check_errors_not_found(tmpdir, package, tagscheck): """Test packages for checks summary-too-long, summary-not-capitalized, summary-ended-with-dot, summary-has-leading-spaces, no-url-tag, description-shorter-than-summary. """ output, test = tagscheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) # Test if a package Summary is not longer than 80 characters assert 'W: summary-too-long' not in out # Test if Summary starts with a capitalized word assert 'W: summary-not-capitalized' not in out # Test if a package Summary does not end with a period assert 'W: summary-ended-with-dot' not in out # Test if a package Summary does not begin with a leading space assert 'W: summary-has-leading-spaces' not in out # Test if a package contains a Url: tag assert 'W: no-url-tag' not in out # Test if package no shorter description than Summary assert 'W: description-shorter-than-summary' not in out @pytest.mark.parametrize('package', ['binary/misc-warnings']) def test_check_misc_warning(tmpdir, package, tagscheck): """Test package for check tag-in-description, name-repeated-in-summary, invalid-url.""" output, test = tagscheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) # Test if a package has a tag such as Name: in the description assert 'W: tag-in-description Name:' in out # Test if a package has it's name repeated in the summary tag assert 'W: name-repeated-in-summary misc-warnings' in out # Test if a package has a invalid url value in the URL: tag assert 'W: invalid-url URL so;mething.' in out @pytest.mark.parametrize('package', ['binary/misc-no-warnings']) def test_check_misc_warning_not_found(tmpdir, package, tagscheck): """Test package for check not in out tag-in-description, name-repeated-in-summary, invalid-url.""" output, test = tagscheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) # Test if a package does not have a tag in description assert 'W: tag-in-description' not in out # Test if a package does not have a name repeated in the summary tag value assert 'W: name-repeated-in-summary' not in out # Test if a package does not have a invalid url in the URL tag value assert 'W: invalid-url' not in out @pytest.mark.parametrize('package', ['binary/invalid-dependency']) def test_check_invalid_dependency(tmpdir, package, tagscheck): """Test if a package has invalid-dependency, no-description-tag, unreasonable-epoch.""" output, test = tagscheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) # Test if a package has a Epoch tag value greater than 99 assert 'W: unreasonable-epoch 100' in out # Test if a package has Requires: starts with /usr/local assert 'E: invalid-dependency /usr/local/something' in out # Test if a package has empty or no description tag assert 'E: no-description-tag' in out @pytest.mark.parametrize('package', ['binary/random-exp']) def test_package_random_warnings(tmpdir, package, tagscheck): """Test if a package has check, - in out, obsolete-not-provided description-line-too-long, - not in out, invalid-dependency, unreasonable-epoch, no-description-tag, self-obsoletion.""" output, test = tagscheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) # Test if a package that was obsoleted is still provided # in newer package to avoid unnecessary dependency breakage assert 'W: obsolete-not-provided' in out # Test if a package has a description longer than 79 characters assert 'E: description-line-too-long This is ridiculously long description that has no meaning but is used to test the check description-line-too-long.' in out # Test if a package has a Requires tag value start with # string other than /usr/local/ assert 'E: invalid-dependency' not in out # Test if a package has a Epoch tag value less than or equal to 99 assert 'W: unreasonable-epoch' not in out # Test if a package has a non-empty description tag assert 'W: no-description-tag' not in out # Test if a package does not have itself in Obsoletes: tag value assert 'W: self-obsoletion' not in out @pytest.mark.parametrize('package', ['binary/random-devel']) def test_package_random_exp(tmpdir, package, tagscheck): """Test if a package check, - in out, self-obsoletion, - not in out, obsolete-not-provided, description-line-too-long, devel-dependency.""" output, test = tagscheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) # Test if a package obsoletes itself i.e. Obsoletes: random-devel assert 'W: self-obsoletion random-devel obsoletes random-devel' in out # Test if a obsoleted package has been provided to # to avoid unnecessary breakage i.e. Requires: python assert 'W: obsolete-not-provided' not in out # Test if a package has a description line not greater 79 characters assert 'E: description-line-too-long' not in out # Test if a package is a *-devel package and requires a devel dependency assert 'W: devel-dependency' not in out @pytest.mark.parametrize('package', ['binary/requires-on-release']) def test_check_requires_on_release(tmpdir, package, tagscheck): """Test if a package check, - in out, requires-on-release.""" output, test = tagscheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) # Test if a package requires specific release of another package assert 'W: requires-on-release baz = 2.1-1' in out @pytest.mark.parametrize('package', ['binary/invalid-license']) def test_check_invalid_license(tmpdir, package, tagscheck): """Test if a package check, - in out, invalid-license, - not in out, requires-on-release.""" CONFIG.configuration['ValidLicenses'] = ['MIT'] output = Filter(CONFIG) test = TagsCheck(CONFIG, output) test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) # Test if a package has a License: tag value different from # ValidLicense = [] list in configuration assert 'W: invalid-license Apache License' in out # Test if a package does not Requires: a specific version of a package assert 'W: requires-on-release' not in out @pytest.mark.parametrize('package', ['binary/not-standard-release-extension']) def test_package_not_std_release_extension(tmpdir, package, tagscheck): """Test if package has check, - in out, not-standard-release-extension - not in out, invalid-license.""" CONFIG.configuration['ReleaseExtension'] = 'hello$' CONFIG.configuration['ValidLicenses'] = ['Apache-2.0 License'] output = Filter(CONFIG) test = TagsCheck(CONFIG, output) test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) # Test if a package has a ReleaseExtension regex does not match with the Release: tag value expression # i.e. Release tag value must not match regex expression 'hello$' assert 'W: not-standard-release-extension 1.1' in out # Test if a package does have the same License value as defined in the ValidLicense in configdefaults assert 'W: invalid-license Apache-2.0 License' not in out @pytest.mark.parametrize('package', ['binary/non-standard-group']) def test_check_non_standard_group(tmpdir, package, tagscheck): """Test if a package has check, - in out, non-standard-group - not in out, not-standard-release-extension.""" CONFIG.configuration['ValidGroups'] = ['Devel/Something'] CONFIG.configuration['ReleaseExtension'] = '0' output = Filter(CONFIG) test = TagsCheck(CONFIG, output) test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) # Test if a package has a different Group: tag value than ValidGroups = [] assert 'W: non-standard-group non/standard/group' in out # Test if a package matches the Release tag regex assert 'not-standard-release-extension 0' not in out @pytest.mark.parametrize('package', ['binary/dev-dependency']) def test_package_dev_dependency(tmpdir, package, tagscheck): """Test if a package check, - in out, devel-dependency, - not in out, non-standard-group.""" CONFIG.configuration['ValidGroups'] = ['Devel/Something'] output = Filter(CONFIG) test = TagsCheck(CONFIG, output) test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) # Test if a package is not a devel package itself but requires a devel dependency assert 'E: devel-dependency glibc-devel' in out # Test if a package does not have a Group tag assert 'W: non-standard-group Devel/Something' not in out @pytest.mark.parametrize('package', ['binary/summary-on-multiple-lines']) def test_summary_on_multiple_lines(tmpdir, package, tagscheck): # Test if a package has summary on multiple lines. output, test = tagscheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: summary-on-multiple-lines' in out rpmlint-2.2.0+ds1/test/test_tmp_files.py000066400000000000000000000035061415540642600202500ustar00rootroot00000000000000import pytest from rpmlint.checks.TmpFilesCheck import TmpFilesCheck from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package @pytest.fixture(scope='function', autouse=True) def tmpfilescheck(): CONFIG.info = True output = Filter(CONFIG) test = TmpFilesCheck(CONFIG, output) return output, test @pytest.mark.parametrize('package', ['binary/tempfiled']) def test_tmpfiles(tmpdir, package, tmpfilescheck): output, test = tmpfilescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'W: pre-with-tmpfile-creation ' not in out assert 'W: post-without-tmpfile-creation /usr/lib/tmpfiles.d/krb5.conf' in out assert 'W: tmpfile-not-in-filelist /var/lib/kerberos' in out assert 'W: tmpfile-not-regular-file /usr/lib/tmpfiles.d/symlink.conf' in out @pytest.mark.parametrize('package', ['binary/systemd-tmpfiles']) def test_tmpfiles2(tmpdir, package, tmpfilescheck): output, test = tmpfilescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'W: pre-with-tmpfile-creation /usr/lib/tmpfiles.d/systemd-tmpfiles.conf' in out assert 'W: post-without-tmpfile-creation' in out assert 'W: tmpfile-not-in-filelist /run/my_new_directory' in out assert 'W: tmpfile-not-regular-file' not in out @pytest.mark.parametrize('package', ['binary/systemd-tmpfiles_correct']) def test_tmpfiles_correct(tmpdir, package, tmpfilescheck): output, test = tmpfilescheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'W: pre-with-tmpfile-creation' not in out assert 'W: post-without-tmpfile-creation' not in out assert 'W: tmpfile-not-regular-file' not in out assert 'W: tmpfile-not-in-filelist' not in out rpmlint-2.2.0+ds1/test/test_xinetd.py000066400000000000000000000011641415540642600175570ustar00rootroot00000000000000import pytest from rpmlint.checks.XinetdDepCheck import XinetdDepCheck from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package @pytest.fixture(scope='function', autouse=True) def xinetdcheck(): CONFIG.info = True output = Filter(CONFIG) test = XinetdDepCheck(CONFIG, output) return output, test @pytest.mark.parametrize('package', ['binary/needxinetd']) def test_xinetd(tmpdir, package, xinetdcheck): output, test = xinetdcheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'E: obsolete-xinetd-requirement' in out rpmlint-2.2.0+ds1/test/test_zip.py000066400000000000000000000037131415540642600170700ustar00rootroot00000000000000import pytest from rpmlint.checks.ZipCheck import ZipCheck from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package @pytest.fixture(scope='function', autouse=True) def zipcheck(): CONFIG.info = True output = Filter(CONFIG) test = ZipCheck(CONFIG, output) return output, test @pytest.mark.parametrize('package', ['binary/bad-crc-uncompressed']) def test_bad_crc_and_compression(tmpdir, package, zipcheck): output, test = zipcheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'bad-crc-in-zip' in out assert 'zip fails the CRC check' in out assert 'uncompressed-zip' in out assert 'zip file is not compressed' in out @pytest.mark.parametrize('package', ['binary/asm']) def test_classpath_and_index(tmpdir, package, zipcheck): output, test = zipcheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'class-path-in-manifest' in out assert 'jar contains a hardcoded Class-Path' in out assert 'jar-not-indexed' in out assert 'jar file is not indexed' in out @pytest.mark.parametrize('package', ['binary/ruby2.5-rubygem-rubyzip-testsuite']) def test_zip1(tmpdir, package, zipcheck): output, test = zipcheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) # these are PW protected not broken so do not error about them assert 'W: unable-to-read-zip' in out # there is a zip with no crc errors assert 'bad-crc-in-zip' not in out # there is zip with only 0 size files, which is not "uncompressed" assert 'uncompressed-zip' not in out @pytest.mark.parametrize('package', ['binary/texlive-codepage-doc']) def test_zip2(tmpdir, package, zipcheck): output, test = zipcheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'W: unable-to-read-zip' in out rpmlint-2.2.0+ds1/test/test_zypp_syntax.py000066400000000000000000000015151415540642600206740ustar00rootroot00000000000000import pytest from rpmlint.checks.ZyppSyntaxCheck import ZyppSyntaxCheck from rpmlint.filter import Filter from Testing import CONFIG, get_tested_package @pytest.fixture(scope='function', autouse=True) def zyppsyntaxcheck(): CONFIG.info = True output = Filter(CONFIG) test = ZyppSyntaxCheck(CONFIG, output) return output, test @pytest.mark.parametrize('package', ['binary/packageand']) def test_packageand(tmpdir, package, zyppsyntaxcheck): output, test = zyppsyntaxcheck test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) assert 'suse-zypp-packageand packageand(c:d)' in out assert 'suse-zypp-packageand packageand(a:b)' in out assert '(a and b)' not in out assert 'bee' not in out assert 'suse-zypp-otherproviders otherproviders(yast2_theme)' in out rpmlint-2.2.0+ds1/tools/000077500000000000000000000000001415540642600150325ustar00rootroot00000000000000rpmlint-2.2.0+ds1/tools/generate-fedora-users-groups.py000077500000000000000000000015521415540642600231160ustar00rootroot00000000000000#!/usr/bin/python3 """ This sciptt is used to generate values for config/Fedora/configs/Fedora/users-groups.toml """ import os import re users = set() groups = set() uidgid_regex = re.compile(r'^\s*(\S+)\s+(-|\d+)\s+(-|\d+|\(\d+\))\s') for uidgid_file in ['/usr/share/doc/setup/uidgid']: if os.path.exists(uidgid_file): fobj = open(uidgid_file) try: for line in fobj.read().strip().splitlines(): res = uidgid_regex.search(line) if res: name = res.group(1) if res.group(2) != '-': users.add(name) if res.group(3) != '-' and '(' not in res.group(3): groups.add(name) del res del line finally: fobj.close() del fobj print(users) print(groups) rpmlint-2.2.0+ds1/tools/generate-isocodes.py000077500000000000000000000034631415540642600210150ustar00rootroot00000000000000#!/usr/bin/python3 # Generate ISO codes for use with e.g. locale subdir checks # http://alioth.debian.org/projects/pkg-isocodes/ import codecs import json import os from pprint import pprint import sys from urllib.request import urlopen iso_3166_1_url = os.environ.get('ISO_3166_1_URL', 'https://salsa.debian.org/iso-codes-team/iso-codes/raw/main/data/iso_3166-1.json') iso_639_3_url = os.environ.get('ISO_639_3_URL', 'https://salsa.debian.org/iso-codes-team/iso-codes/raw/main/data/iso_639-3.json') iso_639_2_url = os.environ.get('ISO_639_2_URL', 'https://salsa.debian.org/iso-codes-team/iso-codes/raw/main/data/iso_639-2.json') langs = set() countries = set() # country codes (2 letters) with urlopen(iso_3166_1_url) as f: data = json.load(codecs.getreader('utf-8')(f)) for entry in data['3166-1']: countries.add(entry['alpha_2']) # language codes (2 or 3 letters, 3 only for ones we don't have 2-letter one) with urlopen(iso_639_3_url) as f: data = json.load(codecs.getreader('utf-8')(f)) for entry in data['639-3']: langs.add(entry.get('alpha_2') or entry['alpha_3']) # Need to check iso-639-2 for collective language codes not in iso-639-3 with urlopen(iso_639_2_url) as f: data = json.load(codecs.getreader('utf-8')(f)) for entry in data['639-2']: entry_code = entry.get('alpha_2') or entry['alpha_3'] if entry_code not in langs: langs.add(entry_code) # Note that we are not pprint()ing the set directly because with # Python 3 it results in curly brace set initializers that are not # compatible with Python 2.6, do it with set([...]) instead. print('# flake8: noqa') print('# Generated with {}'.format(sys.argv[0])) print('') print('LANGUAGES = set(') pprint(sorted(langs)) print(')') print('') print('COUNTRIES = set(') pprint(sorted(countries)) print(')') rpmlint-2.2.0+ds1/tools/generate-suse-licenses.py000077500000000000000000000026631415540642600217700ustar00rootroot00000000000000#!/usr/bin/python3 import os import requests SUSE_EXCEPTIONS = """ AGPL-3.0 AGPL-3.0+ GFDL-1.1 GFDL-1.1+ GFDL-1.2 GFDL-1.2+ GFDL-1.3 GFDL-1.3+ GPL-3.0-with-GCC-exception \ GPL-2.0-with-classpath-exception GPL-2.0-with-font-exception SUSE-LGPL-2.1+-with-GCC-exception SUSE-NonFree \ GPL-1.0+ GPL-1.0 GPL-2.0+ GPL-2.0 GPL-3.0+ GPL-3.0 LGPL-2.0 LGPL-2.0+ LGPL-2.1+ LGPL-2.1 LGPL-3.0+ LGPL-3.0 """ IGNORED_FOR_PLUS = (' with ', '-with-', ' or ') licenses = requests.get('https://raw.githubusercontent.com/openSUSE/obs-service-format_spec_file/master/licenses_changes.txt') with open('../configs/openSUSE/licenses.toml', 'w') as wfile: script_name = os.path.basename(__file__) wfile.write('# Generated with %s script:\n' % script_name) wfile.write('ValidLicenses = [\n') added = set() for line in licenses.text.splitlines(): if line == 'First line': continue name = line.strip().split('\t')[0] if name not in added: wfile.write(f' "{name}",\n') added.add(name) if not name.endswith('+') and not any(i in name.lower() for i in IGNORED_FOR_PLUS): plus_name = f'{name}+' if plus_name not in added: wfile.write(f' "{plus_name}",\n') added.add(plus_name) wfile.write(' # SUSE EXCEPTIONS\n') for name in SUSE_EXCEPTIONS.strip().split(' '): wfile.write(f' "{name}",\n') wfile.write(']\n')