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
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
|
#!/usr/bin/env ruby
require 'getoptlong'
require 'readline'
require 'singleton'
require 'yaml'
# Monkey-path the Array class.
class Array
# ["foo", {"foo" => 1}].cleanup => [{"foo" => 1}]
# If the key in a Hash element of an Array is also present as an element of
# the Array, delete the latter.
def cleanup
array = self.dup
self.grep(Hash).map(&:keys).flatten.each do |x|
array.delete x
end
array
end
end
# Class handles configuration parameters.
class FlagsConfig < Hash
# This is a singleton class.
include Singleton
# Merges a Hash or YAML file (containing a Hash) with itself.
def load config
if config.class == Hash
merge! config
return
end
unless config.nil?
merge_yaml! config
end
end
# Merge Config Hash with Hash in YAML file.
def merge_yaml! path
merge!(load_file path) do |key, old, new|
(old + new).uniq.cleanup if old.is_a? Array and new.is_a? Array
end
end
# Load YAML file and work around tabs not working for identation.
def load_file path
YAML.load open(path).read.gsub(/\t/, ' ')
rescue Psych::SyntaxError => e
print path, ':', e.message.split(':').last, "\n"
exit 1
end
end
# A method to print a beautiful usage message.
def usage
$stderr.puts <<EOF
#{File.basename($0)} [options] [filters]
OPTIONS
-c, --config Override default configuration paths. Requires one
argument. Can contain globs (escape them in some shells
(zsh for example)).
-h, --help This help.
-p, --prepend Do not change anything.
-y, --yes Non-interactive mode. Assume yes on questions.
-x, --xattr Sets the PaX flags through setfattr, underlying
filesystems need xattr support.
FILTERS
Only change flags for paths, which contain one of these filters as a string.
EOF
exit 1
end
# This iterates each config entry (which matches the filters). It yields flags,
# entry, pattern and path of the config entry to the block code.
def each_entry config, filters
config.each do |flags, entries|
entries.each do |entry|
# Distinguish easy (String) and complex (Hash) config entries.
if entry.is_a? String
pattern = entry
elsif entry.is_a? Hash
pattern = entry.keys.first
end
# Skip this entry, if its path pattern does not contain one of the
# filters.
# TODO Do this for every matching path.
unless filters.empty?
temp_filters = filters.dup
temp_filters.keep_if do |filter|
pattern.downcase.include? filter.downcase
end
next if temp_filters.empty?
end
# If this runs with sudo, the ~ (for the users home path) have to point to
# the user who runs it, not to root.
unless ENV['SUDO_USER'].nil?
paths = File.expand_path pattern.gsub('~', '~' + ENV['SUDO_USER'])
else
paths = File.expand_path pattern
end
# Now yield for every matching path.
Dir.glob(paths).each do |path|
yield flags, entry, pattern, path
end
end
end
end
# Trap SIGINT (ctrl+c)
trap(:INT) { exit 1 }
# Define the possible options.
options = GetoptLong.new(
['--config', '-c', GetoptLong::REQUIRED_ARGUMENT],
['--help', '-h', GetoptLong::NO_ARGUMENT],
['--prepend', '-p', GetoptLong::NO_ARGUMENT],
['--xattr', '-x', GetoptLong::NO_ARGUMENT],
['--yes', '-y', GetoptLong::NO_ARGUMENT],
)
# Initialize option variables.
new_configs = []
prepend = false
yes = false
xattr = false
# Set option variables.
begin
options.each do |option, argument|
case option
when '--config'
new_configs = Dir.glob argument
when '--help'
usage
when '--prepend'
prepend = true
when '--xattr'
xattr = true
when '--yes'
yes = true
end
end
rescue GetoptLong::InvalidOption => e
usage
end
# Whatever is left over is a filter.
filters = ARGV
# Exit if we are not running with root privileges.
if Process.uid != 0
$stderr << "Root privileges needed.\n"
exit 1
end
# Either default config paths or overridden ones.
config_paths = if new_configs.empty?
['/etc/pax-flags-libre/*.conf', '/usr/share/pax-flags-libre/*.conf']
else
new_configs
end
# Initialize the singleton config object...
config = FlagsConfig.instance
# ... and load every config file.
config_paths.each do |path|
Dir.glob(path).each do |file|
config.load file
end
end
# Helper text for simple entries.
puts <<EOF
Some programs do not work properly without deactivating some of the PaX
features. Please close all instances of them if you want to change the
configuration for the following binaries.
EOF
# Show every simple entry.
each_entry config, filters do |flags, entry, pattern, path|
puts ' * ' + path if File.exists? path and entry.is_a? String
end
# Let us sum up the complex entries...
autopaths = []
each_entry config, filters do |flags, entry, pattern, path|
if File.exists? path and entry.is_a? Hash
autopaths.push path if not (entry.nil? and entry[path]['skip'])
end
end
# ... to decide, if we need to print them.
unless autopaths.empty?
puts <<EOF
For the following programs there are also changes neccessary but you do not have
to close or restart instances of them manually.
EOF
autopaths.each do |path|
puts ' * ' + path
end
end
puts
puts 'Continue writing PaX headers? [Y/n]'
$stdout.flush
unless yes
a = Readline.readline.chomp.downcase
exit 1 if a.downcase != 'y' unless a.empty?
end
# Iterate each entry to actually set the flags.
each_entry config, filters do |flags, entry, pattern, path|
if File.exists? path
e = entry[pattern]
actions = %w(status start stop)
start_again = false
# Get action commands from entries config.
status = e['status']
start = e['start']
stop = e['stop']
# If the type attribute is set to systemd, we set the action command
# variables again but to systemd defaults.
if e['type'] == 'systemd'
name = e['systemd_name'] || File.basename(path)
actions.each do |action|
eval "#{action} = \"systemctl #{action} #{name}.service\""
end
end
# If the entry is complex, stop it if it is running.
if entry.is_a? Hash
if status and system(status + '> /dev/null')
system stop unless prepend
start_again = true if start
end
end
if xattr
# setfattr seems to be picky about the order of the flags,
# rearrange it beforehand
xflags = flags[/[Pp]/] + flags[/[Ee]/] + flags[/[Mm]/] +
flags[/[Rr]/] + flags[/[Ss]/]
print xflags, ' ', path, "\n"
else
print flags, ' ', path, "\n"
end
# Set the flags and notify the user.
unless prepend
if xattr
`setfattr -n user.pax.flags -v #{xflags} "#{path}"`
else
header = 'c'
header = 'C' if e['header'] == 'create'
`paxctl -#{header}#{flags} "#{path}"`
end
end
# Start the complex entries service again, if it is neccessary.
system start unless prepend if start_again
end
end
|