forked from rpm-software-management/rpmlint
-
Notifications
You must be signed in to change notification settings - Fork 0
/
PostCheck.py
236 lines (196 loc) · 8.92 KB
/
PostCheck.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# -*- coding: utf-8 -*-
#############################################################################
# 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 rpm
import AbstractCheck
import Config
from Filter import addDetails, printError, printWarning
import Pkg
RPM_SCRIPTLETS = ('pre', 'post', 'preun', 'postun', 'pretrans', 'posttrans',
'trigger', 'triggerin', 'triggerprein', 'triggerun',
'triggerpostun', 'verifyscript', 'filetriggerin',
'filetrigger', 'filetriggerun', 'filetriggerpostun',
'transfiletriggerin', 'transfiletrigger',
'transfiletriggerun', 'transfiletriggerun',
'transfiletriggerpostun')
DEFAULT_VALID_SHELLS = ('<lua>',
'/bin/sh',
'/bin/bash',
'/sbin/sash',
'/usr/bin/perl',
'/sbin/ldconfig',
)
DEFAULT_EMPTY_SHELLS = ('/sbin/ldconfig',
)
valid_shells = Config.getOption('ValidShells', DEFAULT_VALID_SHELLS)
empty_shells = Config.getOption('ValidEmptyShells', DEFAULT_EMPTY_SHELLS)
# 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
# TODO: test that "prog" is available/executable
tmpfile, tmpname = Pkg.mktemp()
try:
tmpfile.write(script)
tmpfile.close()
ret = Pkg.getstatusoutput((prog, commandline, tmpname))
finally:
tmpfile.close()
os.remove(tmpname)
return ret[0]
class PostCheck(AbstractCheck.AbstractCheck):
def __init__(self):
AbstractCheck.AbstractCheck.__init__(self, 'PostCheck')
def check_binary(self, pkg):
prereq = [x[0] for x in pkg.prereq()]
files = pkg.files()
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, files, prog, script, tag[2], prereq)
else:
prog = pkg[tag[1]]
for idx in range(0, len(prog)):
self.check_aux(
pkg, files, prog[idx], script[idx], tag[2], prereq)
ghost_files = pkg.ghostFiles()
if ghost_files:
postin = pkg[rpm.RPMTAG_POSTIN]
prein = pkg[rpm.RPMTAG_PREIN]
for f in ghost_files:
if f in pkg.missingOkFiles():
continue
if not postin and not prein:
printWarning(pkg, 'ghost-files-without-postin')
if (not postin or f not in postin) and \
(not prein or f not in prein):
printWarning(pkg,
'postin-without-ghost-file-creation', f)
def check_aux(self, pkg, files, prog, script, tag, prereq):
if script:
if prog:
if prog not in valid_shells:
printError(pkg, 'invalid-shell-in-' + tag, prog)
if prog in empty_shells:
printError(pkg, 'non-empty-' + tag, prog)
if prog in syntaxcheck_shells or prog == '/usr/bin/perl':
if percent_regex.search(script):
printWarning(pkg, 'percent-in-' + tag)
if bracket_regex.search(script):
printWarning(pkg, 'spurious-bracket-in-' + tag)
res = dangerous_command_regex.search(script)
if res:
printWarning(pkg, 'dangerous-command-in-' + tag,
res.group(2))
res = selinux_regex.search(script)
if res:
printError(pkg, 'forbidden-selinux-command-in-' + tag,
res.group(2))
if 'update-menus' in script:
menu_error = True
for f in files:
if menu_regex.search(f):
menu_error = False
break
if menu_error:
printError(pkg, 'update-menus-without-menu-file-in-' +
tag)
if tmp_regex.search(script):
printError(pkg, 'use-tmp-in-' + tag)
for c in prereq_assoc:
if c[0].search(script):
found = False
for p in c[1]:
if p in prereq or p in files:
found = True
break
if not found:
printError(pkg, 'no-prereq-on', c[1][0])
if prog in syntaxcheck_shells:
if incorrect_shell_script(prog, script):
printError(pkg, 'shell-syntax-error-in-' + tag)
if home_regex.search(script):
printError(pkg, 'use-of-home-in-' + tag)
res = bogus_var_regex.search(script)
if res:
printWarning(pkg, 'bogus-variable-use-in-' + tag,
res.group(1))
if prog == '/usr/bin/perl':
if incorrect_perl_script(prog, script):
printError(pkg, 'perl-syntax-error-in-' + tag)
elif prog.endswith('sh'):
res = single_command_regex.search(script)
if res:
printWarning(pkg, 'one-line-command-in-' + tag,
res.group(1))
elif prog not in empty_shells and prog in valid_shells:
printWarning(pkg, 'empty-' + tag)
# Create an object to enable the auto registration of the test
check = PostCheck()
# Add information about checks
addDetails(
'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, RPM_SCRIPTLETS):
addDetails(
'one-line-command-in-%s' % scriptlet,
'''You should use %s -p <command> instead of using:
%s
<command>
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.''',
)
# PostCheck.py ends here