Skip to content

Commit 843dd41

Browse files
Implemented .d for config files. Fixed unit tests and updated README to reflect this.
1 parent 84c78dc commit 843dd41

File tree

3 files changed

+113
-45
lines changed

3 files changed

+113
-45
lines changed

README.md

+22-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ not root-owned (paxrat will not set PaX flags on non-root owned binaries unless
3131
this is set to `true`). By default paxrat will look for binary divertions using
3232
`dpkg-divert`, this can be disabled by using the `nodivert` setting.
3333

34+
The default configuration file for paxrat is located in
35+
`/etc/paxrat/paxrat.conf`. Running paxrat with no configuration file argument
36+
will automatically use this file to set PaX flags.
37+
38+
paxrat also supports optional configuration files from the
39+
`/etc/paxrat/conf.d/` directory files. This is for user created configuration.
40+
paxrat must be run with no `-c` argument to use the files in this directory.
41+
3442
## Configuration example
3543

3644
The following is an example configuration:
@@ -51,13 +59,26 @@ The following is an example configuration:
5159

5260
# Usage
5361

62+
## Default mode
63+
64+
When paxrat is run without a configuration file (without `-c`) argument, it will use
65+
the configuration file found in `/etc/paxrat/paxrat.conf` to set PaX flags.
66+
It will also scan `/etc/paxrat/conf.d/` for additional configuration files. The
67+
`/etc/paxrat/conf.d/` directory can be used for user configurations. This is
68+
the *preferred* mode of operation.
69+
70+
71+
```sh
72+
$ sudo paxrat
73+
```
74+
5475
## Set flags on a single binary
5576

5677
```sh
5778
$ sudo paxrat -s pm -b /usr/lib/iceweasel/iceweasel
5879
```
5980

60-
## Set all flags from a config file
81+
## Set all flags from a non-default config file
6182

6283
```sh
6384
$ sudo paxrat -c paxrat.conf

paxrat.go

+77-29
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"os"
1616
"os/exec"
1717
"os/user"
18+
"path"
1819
"path/filepath"
1920
"regexp"
2021
"strings"
@@ -25,6 +26,11 @@ import (
2526

2627
const ioctlReadTermios = 0x5401
2728

29+
const configDirectory = "/etc/paxrat/"
30+
31+
var defaultConfigPath = path.Join(configDirectory, "paxrat.conf")
32+
var optionalConfigDirectory = path.Join(configDirectory, "conf.d/")
33+
2834
var configvar string
2935
var testvar bool
3036
var xattrvar bool
@@ -36,19 +42,20 @@ var nodivertvar bool
3642
var replacementvar string
3743
var quietvar bool
3844
var verbosevar bool
45+
var configs []Config
3946

4047
type Setting struct {
4148
Flags string `json:"flags"`
4249
Nonroot bool `json:"nonroot,omitempty"`
4350
Nodivert bool `json:"nodivert,omitempty"`
4451
}
52+
4553
type Config struct {
4654
Settings map[string]Setting
4755
}
4856

4957
var InotifyFlags uint32
5058
var InotifyDirFlags uint32
51-
var Conf *Config
5259
var LogWriter *syslog.Writer
5360
var SyslogError error
5461

@@ -68,8 +75,7 @@ func init() {
6875
inotify.IN_MOVED_TO)
6976
InotifyDirFlags = (inotify.IN_DONT_FOLLOW | inotify.IN_CREATE |
7077
inotify.IN_DELETE_SELF | inotify.IN_MOVE_SELF | inotify.IN_MOVED_TO)
71-
Conf = new(Config)
72-
flag.StringVar(&configvar, "c", "/etc/paxrat/paxrat.conf",
78+
flag.StringVar(&configvar, "c", defaultConfigPath,
7379
"Pax flags configuration file")
7480
flag.BoolVar(&testvar, "t", false,
7581
"Test the config file and then exit")
@@ -94,10 +100,11 @@ func init() {
94100

95101
}
96102

97-
func (conf *Config) readConfig(path string) error {
103+
func readConfig(path string) (*Config, error) {
104+
config := new(Config)
98105
file, err := os.Open(path)
99106
if err != nil {
100-
log.Fatal(err)
107+
return config, err
101108
}
102109
scanner := bufio.NewScanner(file)
103110
out := ""
@@ -111,12 +118,23 @@ func (conf *Config) readConfig(path string) error {
111118
fmt.Println(line)
112119
}
113120
}
114-
var data = &conf.Settings
121+
var data = &config.Settings
115122
err = json.Unmarshal([]byte(out), data)
116123
if err != nil {
117-
log.Fatal(err)
124+
return config, err
118125
}
119-
return err
126+
return config, nil
127+
}
128+
129+
func mergeConfigs() *Config {
130+
config := new(Config)
131+
config.Settings = make(map[string]Setting)
132+
for _, conf := range configs {
133+
for name, setting := range conf.Settings {
134+
config.Settings[name] = setting
135+
}
136+
}
137+
return config
120138
}
121139

122140
func pathExists(path string) bool {
@@ -144,10 +162,11 @@ func validateFlags(flags string) error {
144162
var err error
145163
match, _ := regexp.MatchString("(?i)[^pemrxs]", flags)
146164
if match {
147-
err = fmt.Errorf("Bad characters found in PaX flags: %s",
165+
err = fmt.Errorf("bad characters found in PaX flags: %s",
148166
flags)
167+
return err
149168
}
150-
return err
169+
return nil
151170
}
152171

153172
func checkEmulTramp(flags string) string {
@@ -194,6 +213,7 @@ func setFlags(path string, flags string, nonroot, nodivert bool) error {
194213
}
195214
err = validateFlags(flags)
196215
if err != nil {
216+
err = fmt.Errorf("Could not set PaX flags on %s - %s", path, err)
197217
return err
198218
}
199219
supported, err := isXattrSupported()
@@ -218,7 +238,7 @@ func setFlags(path string, flags string, nonroot, nodivert bool) error {
218238
return err
219239
}
220240
linkUid := fiPath.Sys().(*syscall.Stat_t).Uid
221-
// Throw error if nonroot option is not set but the file is owned by a user other than root
241+
// Report error if nonroot option is not set but the file is owned by a user other than root
222242
if !nonroot && linkUid > 0 {
223243
err = fmt.Errorf(
224244
"Cannot set PaX flags on %s. Owner of symlink did not match owner of symlink target\n",
@@ -241,7 +261,7 @@ func setFlags(path string, flags string, nonroot, nodivert bool) error {
241261
return err
242262
}
243263
targetUid := fiRPath.Sys().(*syscall.Stat_t).Uid
244-
// If nonroot is set then throw an error if the owner of the file is different than the owner of the symlink target
264+
// If nonroot is set then report an error if the owner of the file != owner of the symlink target
245265
if nonroot && targetUid != linkUid {
246266
err = fmt.Errorf(
247267
"Cannot set PaX flags on %s. Owner of symlink did not match owner of symlink target\n",
@@ -275,8 +295,8 @@ func setFlagsWatchMode(watcher *inotify.Watcher, path string, flags string, nonr
275295
return nil
276296
}
277297

278-
func setFlagsFromConfig() {
279-
for path, setting := range (*Conf).Settings {
298+
func setFlagsFromConfig(conf *Config) {
299+
for path, setting := range conf.Settings {
280300
err := setFlags(path, setting.Flags, setting.Nonroot, setting.Nodivert)
281301
if err != nil {
282302
log.Println(err)
@@ -359,13 +379,13 @@ func addWatchToClosestPath(watcher *inotify.Watcher, path string) {
359379

360380
}
361381

362-
func initWatcher() (*inotify.Watcher, error) {
382+
func initWatcher(config *Config) (*inotify.Watcher, error) {
363383
log.Println("Initializing paxrat watcher")
364384
watcher, err := inotify.NewWatcher()
365385
if err != nil {
366386
return watcher, err
367387
}
368-
for path, setting := range (*Conf).Settings {
388+
for path, setting := range config.Settings {
369389
addWatchToClosestPath(watcher, path)
370390
err = setFlagsWatchMode(watcher, path, setting.Flags, setting.Nonroot, setting.Nodivert)
371391
if err != nil {
@@ -376,32 +396,32 @@ func initWatcher() (*inotify.Watcher, error) {
376396
}
377397

378398
// TODO: Resolve some corner cases like watches not set after create, delete, create, move
379-
func runWatcher(watcher *inotify.Watcher) {
399+
func runWatcher(watcher *inotify.Watcher, config *Config) {
380400
log.Println("Starting paxrat watcher")
381401
for {
382402
select {
383403
case ev := <-watcher.Event:
384404
if ev.Mask == inotify.IN_CREATE {
385-
if _, ok := (*Conf).Settings[ev.Name]; ok {
405+
if _, ok := (*config).Settings[ev.Name]; ok {
386406
watcher.AddWatch(ev.Name, InotifyFlags)
387407
log.Printf("File created: %s\n", ev.Name)
388408
}
389409
// Catch directory creation events for non-existent directories in executable path
390410
} else if ev.Mask == (inotify.IN_CREATE | inotify.IN_ISDIR) {
391-
for path, _ := range (*Conf).Settings {
411+
for path, _ := range (*config).Settings {
392412
if strings.HasPrefix(path, ev.Name) {
393413
addWatchToClosestPath(watcher, path)
394414
}
395415
}
396416
} else if ev.Mask == inotify.IN_DELETE_SELF || ev.Mask == inotify.IN_MOVE_SELF {
397-
if _, ok := (*Conf).Settings[ev.Name]; ok {
417+
if _, ok := (*config).Settings[ev.Name]; ok {
398418
log.Printf("File deleted: %s\n", ev.Name)
399419
parent := filepath.Dir(ev.Name)
400420
watcher.AddWatch(parent, InotifyDirFlags)
401421
continue
402422
}
403423
} else if ev.Mask == inotify.IN_ATTRIB {
404-
if _, ok := (*Conf).Settings[ev.Name]; ok {
424+
if _, ok := (*config).Settings[ev.Name]; ok {
405425
exists := pathExists(ev.Name)
406426
if !exists {
407427
log.Printf("File deleted: %s\n", ev.Name)
@@ -414,7 +434,7 @@ func runWatcher(watcher *inotify.Watcher) {
414434
}
415435
}
416436
}
417-
if settings, ok := (*Conf).Settings[ev.Name]; ok {
437+
if settings, ok := (*config).Settings[ev.Name]; ok {
418438
if ev.Mask != inotify.IN_IGNORED {
419439
err := setFlagsWatchMode(watcher, ev.Name, settings.Flags, settings.Nonroot, settings.Nodivert)
420440
if err != nil {
@@ -436,9 +456,9 @@ func main() {
436456
}
437457
if testvar {
438458
log.Printf("Reading config from: %s\n", configvar)
439-
err := Conf.readConfig(configvar)
459+
_, err := readConfig(configvar)
440460
if err != nil {
441-
log.Fatal(err)
461+
log.Fatalf("Could not read config: %s - %s\n", configvar, err)
442462
}
443463
log.Printf("Configuration is valid\n")
444464
os.Exit(0)
@@ -449,22 +469,50 @@ func main() {
449469
}
450470
os.Exit(0)
451471
} else {
472+
var mergedConfig *Config
452473
if xattrvar {
453474
log.Println("Running forced xattr mode")
454475
}
455476
log.Printf("Reading config from: %s\n", configvar)
456-
err := Conf.readConfig(configvar)
477+
conf, err := readConfig(configvar)
457478
if err != nil {
458-
log.Fatal(err)
479+
log.Fatalf("Could not read config: %s, %s\n", configvar, err)
480+
}
481+
configs = append(configs, (*conf))
482+
if configvar == defaultConfigPath &&
483+
pathExists(optionalConfigDirectory) {
484+
files, err := ioutil.ReadDir(optionalConfigDirectory)
485+
if err != nil {
486+
log.Printf(
487+
"Could not read optional config directory: %s - %s\n",
488+
optionalConfigDirectory, err)
489+
}
490+
for _, confFile := range files {
491+
if !confFile.IsDir() {
492+
confFilePath := path.Join(optionalConfigDirectory,
493+
confFile.Name())
494+
log.Printf("Reading config from: %s", confFilePath)
495+
if pathExists(confFilePath) {
496+
conf, err := readConfig(confFilePath)
497+
if err != nil {
498+
// Do not die fatally on bad optional config
499+
log.Printf("Could not read config: %s - %s\n",
500+
confFile.Name(), err)
501+
}
502+
configs = append(configs, (*conf))
503+
}
504+
}
505+
}
459506
}
507+
mergedConfig = mergeConfigs()
460508
if watchvar {
461-
watcher, err := initWatcher()
509+
watcher, err := initWatcher(mergedConfig)
462510
if err != nil {
463511
log.Fatalf("Could not initialize watcher: %s\n", err)
464512
}
465-
runWatcher(watcher)
513+
runWatcher(watcher, mergedConfig)
466514
} else {
467-
setFlagsFromConfig()
515+
setFlagsFromConfig(mergedConfig)
468516
os.Exit(0)
469517
}
470518
}

0 commit comments

Comments
 (0)