-// Copyright (C) 2021 Simon Ruderich
-//
-// 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 3 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, see <http://www.gnu.org/licenses/>.
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright (C) 2021-2024 Simon Ruderich
package sync
if err != nil {
t.Fatal(err)
}
- defer os.Chdir(cwd)
+ defer os.Chdir(cwd) //nolint:errcheck
err = os.RemoveAll("testdata")
if err != nil {
safcm.MsgSyncReq{
Files: map[string]*safcm.File{
".": {
+ OrigGroup: "group",
Path: ".",
Mode: fs.ModeDir | 0700,
Uid: -1,
Gid: -1,
- OrigGroup: "group",
},
"dir": {
+ OrigGroup: "group",
Path: "dir",
Mode: fs.ModeDir | 0755,
Uid: -1,
Gid: -1,
- OrigGroup: "group",
},
"dir/file": {
+ OrigGroup: "group",
Path: "dir/file",
Mode: 0644,
Uid: -1,
Gid: -1,
Data: []byte("content\n"),
- OrigGroup: "group",
},
},
},
safcm.MsgSyncReq{
Files: map[string]*safcm.File{
".": {
+ OrigGroup: "group",
Path: ".",
Mode: fs.ModeDir | 0700,
Uid: -1,
Gid: -1,
- OrigGroup: "group",
},
"dir": {
+ OrigGroup: "group",
Path: "dir",
Mode: fs.ModeDir | 0755,
Uid: -1,
Gid: -1,
- OrigGroup: "group",
},
"dir/file": {
+ OrigGroup: "group",
Path: "dir/file",
Mode: 0644,
Uid: -1,
Gid: -1,
Data: []byte("content\n"),
- OrigGroup: "group",
},
},
},
safcm.MsgSyncReq{
Files: map[string]*safcm.File{
".": {
+ OrigGroup: "group",
Path: ".",
Mode: fs.ModeDir | 0700,
User: "user",
Uid: 1,
Gid: -1,
- OrigGroup: "group",
},
},
},
safcm.MsgSyncReq{
Files: map[string]*safcm.File{
".": {
+ OrigGroup: "group",
Path: ".",
Mode: fs.ModeDir | 0700,
Uid: -1,
Group: "group",
Gid: 1,
- OrigGroup: "group",
},
},
},
safcm.MsgSyncReq{
Files: map[string]*safcm.File{
"/": {
+ OrigGroup: "group",
Path: "/",
Mode: fs.ModeDir | 0755,
Uid: 0,
Gid: 0,
- OrigGroup: "group",
},
"/etc": {
+ OrigGroup: "group",
Path: "/etc",
Mode: fs.ModeDir | 0755,
Uid: 0,
Gid: 0,
- OrigGroup: "group",
},
"/tmp": {
+ OrigGroup: "group",
Path: "/tmp",
Mode: fs.ModeDir | 0777 | fs.ModeSticky,
Uid: 0,
Gid: 0,
- OrigGroup: "group",
},
},
},
safcm.MsgSyncReq{
Files: map[string]*safcm.File{
".": {
+ OrigGroup: "group",
Path: ".",
Mode: fs.ModeDir | 0700,
Uid: -1,
Gid: -1,
- OrigGroup: "group",
TriggerCommands: []string{
"echo trigger .",
},
},
"dir": {
+ OrigGroup: "group",
Path: "dir",
Mode: fs.ModeDir | 0755,
Uid: -1,
Gid: -1,
- OrigGroup: "group",
TriggerCommands: []string{
"echo trigger dir",
},
},
"dir/file": {
+ OrigGroup: "group",
Path: "dir/file",
Mode: 0644,
Uid: -1,
Gid: -1,
Data: []byte("content\n"),
- OrigGroup: "group",
TriggerCommands: []string{
"echo trigger dir/file",
},
safcm.MsgSyncReq{
Files: map[string]*safcm.File{
".": {
+ OrigGroup: "group",
Path: ".",
Mode: fs.ModeDir | 0700,
Uid: -1,
Gid: -1,
- OrigGroup: "group",
TriggerCommands: []string{
"echo trigger .",
},
},
"dir": {
+ OrigGroup: "group",
Path: "dir",
Mode: fs.ModeDir | 0755,
Uid: -1,
Gid: -1,
- OrigGroup: "group",
TriggerCommands: []string{
"echo trigger dir",
},
},
"dir/file": {
+ OrigGroup: "group",
Path: "dir/file",
Mode: 0644,
Uid: -1,
Gid: -1,
Data: []byte("content\n"),
- OrigGroup: "group",
TriggerCommands: []string{
"echo trigger dir/file",
},
safcm.MsgSyncReq{
Files: map[string]*safcm.File{
".": {
+ OrigGroup: "group",
Path: ".",
Mode: fs.ModeDir | 0700,
Uid: -1,
Gid: -1,
- OrigGroup: "group",
TriggerCommands: []string{
"echo trigger .",
},
},
"dir": {
+ OrigGroup: "group",
Path: "dir",
Mode: fs.ModeDir | 0755,
Uid: -1,
Gid: -1,
- OrigGroup: "group",
TriggerCommands: []string{
"echo trigger dir",
},
},
"dir/file": {
+ OrigGroup: "group",
Path: "dir/file",
Mode: 0644,
Uid: -1,
Gid: -1,
Data: []byte("content\n"),
- OrigGroup: "group",
TriggerCommands: []string{
"echo trigger dir/file",
},
safcm.MsgSyncReq{
Files: map[string]*safcm.File{
".": {
+ OrigGroup: "group",
Path: ".",
Mode: fs.ModeDir | 0700,
Uid: -1,
Gid: -1,
- OrigGroup: "group",
TriggerCommands: []string{
"echo trigger .",
},
},
"dir": {
+ OrigGroup: "group",
Path: "dir",
Mode: fs.ModeDir | 0755,
Uid: -1,
Gid: -1,
- OrigGroup: "group",
TriggerCommands: []string{
"echo trigger dir",
},
},
"dir/file": {
+ OrigGroup: "group",
Path: "dir/file",
Mode: 0644,
Uid: -1,
Gid: -1,
Data: []byte("content\n"),
- OrigGroup: "group",
TriggerCommands: []string{
"echo trigger dir/file",
},
safcm.MsgSyncReq{
Files: map[string]*safcm.File{
".": {
+ OrigGroup: "group",
Path: ".",
Mode: fs.ModeDir | 0700,
Uid: -1,
Gid: -1,
- OrigGroup: "group",
TriggerCommands: []string{
"echo trigger .",
},
},
"dir": {
+ OrigGroup: "group",
Path: "dir",
Mode: fs.ModeDir | 0755,
Uid: -1,
Gid: -1,
- OrigGroup: "group",
TriggerCommands: []string{
"echo trigger dir",
},
},
"dir/file": {
+ OrigGroup: "group",
Path: "dir/file",
Mode: 0644,
Uid: -1,
Gid: -1,
Data: []byte("content\n"),
- OrigGroup: "group",
TriggerCommands: []string{
"echo trigger dir/file",
},
safcm.MsgSyncReq{
Files: map[string]*safcm.File{
"/": {
+ OrigGroup: "group",
Path: "/",
Mode: fs.ModeDir | 0755,
Uid: 0,
Gid: 0,
- OrigGroup: "group",
TriggerCommands: []string{
"echo trigger /",
},
},
"/tmp": {
+ OrigGroup: "group",
Path: "/tmp",
Mode: fs.ModeDir | 0777 | fs.ModeSticky,
Uid: 0,
Gid: 0,
- OrigGroup: "group",
TriggerCommands: []string{
"echo trigger /tmp",
},
},
tmpTestFilePath: {
+ OrigGroup: "group",
Path: tmpTestFilePath,
Mode: 0600,
Uid: -1,
Gid: -1,
- OrigGroup: "group",
TriggerCommands: []string{
"echo trigger /tmp/file",
},
if err != nil {
t.Fatal(err)
}
- defer os.Chdir(cwd)
+ defer os.Chdir(cwd) //nolint:errcheck
err = os.RemoveAll("testdata")
if err != nil {
`4: files: "file" (group): will create`,
`3: files: "file" (group): creating`,
`4: files: "file" (group): creating temporary file ".file*"`,
- `4: files: "file" (group): renaming "./.fileRND"`,
+ `4: files: "file" (group): renaming ".fileRND"`,
},
nil,
},
nil,
},
+ {
+ "file: create, missing parent (dry-run)",
+ safcm.MsgSyncReq{
+ DryRun: true,
+ },
+ &safcm.File{
+ Path: "does-not-exist/file",
+ Mode: 0644,
+ Uid: -1,
+ Gid: -1,
+ Data: []byte("content\n"),
+ OrigGroup: "group",
+ },
+ nil,
+ true,
+ []ft.File{root},
+ safcm.MsgSyncResp{
+ FileChanges: []safcm.FileChange{
+ {
+ Path: "does-not-exist/file",
+ Created: true,
+ New: safcm.FileChangeInfo{
+ Mode: 0644,
+ User: user,
+ Uid: uid,
+ Group: group,
+ Gid: gid,
+ },
+ },
+ },
+ },
+ []string{
+ `4: files: "does-not-exist/file" (group): will create (parent missing)`,
+ `4: files: "does-not-exist/file" (group): dry-run, skipping changes`,
+ },
+ nil,
+ },
+
{
"file: unchanged",
safcm.MsgSyncReq{},
`4: files: "file" (group): permission differs -rwxr-xr-x -> urwxr-xr-x`,
`3: files: "file" (group): updating`,
`4: files: "file" (group): creating temporary file ".file*"`,
- `4: files: "file" (group): renaming "./.fileRND"`,
+ `4: files: "file" (group): renaming ".fileRND"`,
},
nil,
},
`4: files: "file" (group): content differs`,
`3: files: "file" (group): updating`,
`4: files: "file" (group): creating temporary file ".file*"`,
- `4: files: "file" (group): renaming "./.fileRND"`,
+ `4: files: "file" (group): renaming ".fileRND"`,
},
nil,
},
nil,
},
+ {
+ "directory: create, missing parent (dry-run)",
+ safcm.MsgSyncReq{
+ DryRun: true,
+ },
+ &safcm.File{
+ Path: "does-not-exist/dir",
+ Mode: fs.ModeDir | 0755,
+ Uid: -1,
+ Gid: -1,
+ OrigGroup: "group",
+ },
+ nil,
+ true,
+ []ft.File{root},
+ safcm.MsgSyncResp{
+ FileChanges: []safcm.FileChange{
+ {
+ Path: "does-not-exist/dir",
+ Created: true,
+ New: safcm.FileChangeInfo{
+ Mode: fs.ModeDir | 0755,
+ User: user,
+ Uid: uid,
+ Group: group,
+ Gid: gid,
+ },
+ },
+ },
+ },
+ []string{
+ `4: files: "does-not-exist/dir" (group): will create (parent missing)`,
+ `4: files: "does-not-exist/dir" (group): dry-run, skipping changes`,
+ },
+ nil,
+ },
+
{
"directory: unchanged",
safcm.MsgSyncReq{},
`4: files: "path" (group): type differs L--------- -> ----------`,
`3: files: "path" (group): updating`,
`4: files: "path" (group): creating temporary file ".path*"`,
- `4: files: "path" (group): renaming "./.pathRND"`,
+ `4: files: "path" (group): renaming ".pathRND"`,
},
nil,
},
`3: files: "path" (group): updating`,
`4: files: "path" (group): removing (due to type change)`,
`4: files: "path" (group): creating temporary file ".path*"`,
- `4: files: "path" (group): renaming "./.pathRND"`,
+ `4: files: "path" (group): renaming ".pathRND"`,
},
nil,
},
+ {
+ "change: directory to file (non-empty)",
+ safcm.MsgSyncReq{},
+ &safcm.File{
+ Path: "path",
+ Mode: 0666,
+ Uid: -1,
+ Gid: -1,
+ OrigGroup: "group",
+ Data: []byte("content\n"),
+ },
+ func() {
+ ft.CreateDirectory("path", 0777)
+ ft.CreateFile("path/file", "content\n", 0644)
+ },
+ true,
+ []ft.File{
+ root,
+ {
+ Path: "path",
+ Mode: fs.ModeDir | 0777,
+ },
+ {
+ Path: "path/file",
+ Mode: 0644,
+ Data: []byte("content\n"),
+ },
+ },
+ safcm.MsgSyncResp{
+ FileChanges: []safcm.FileChange{
+ {
+ Path: "path",
+ Old: safcm.FileChangeInfo{
+ Mode: fs.ModeDir | 0777,
+ User: user,
+ Uid: uid,
+ Group: group,
+ Gid: gid,
+ },
+ New: safcm.FileChangeInfo{
+ Mode: 0666,
+ User: user,
+ Uid: uid,
+ Group: group,
+ Gid: gid,
+ },
+ },
+ },
+ },
+ []string{
+ `4: files: "path" (group): type differs d--------- -> ----------`,
+ `3: files: "path" (group): updating`,
+ `4: files: "path" (group): removing (due to type change)`,
+ },
+ fmt.Errorf("will not replace non-empty directory, please remove manually"),
+ },
{
"change: directory to symlink",
`4: files: "path" (group): type differs p--------- -> ----------`,
`3: files: "path" (group): updating`,
`4: files: "path" (group): creating temporary file ".path*"`,
- `4: files: "path" (group): renaming "./.pathRND"`,
+ `4: files: "path" (group): renaming ".pathRND"`,
},
nil,
},
nil,
},
+ // Symlink "attacks"
+
+ {
+ "symlink in earlier path component",
+ safcm.MsgSyncReq{},
+ &safcm.File{
+ Path: "dir/file",
+ Mode: 0644,
+ Uid: -1,
+ Gid: -1,
+ OrigGroup: "group",
+ Data: []byte("content"),
+ },
+ func() {
+ ft.CreateDirectory("tmp", 0755)
+ ft.CreateSymlink("dir", "tmp")
+ },
+ false,
+ []ft.File{
+ root,
+ {
+ Path: "dir",
+ Mode: fs.ModeSymlink | 0777,
+ Data: []byte("tmp"),
+ },
+ {
+ Path: "tmp",
+ Mode: fs.ModeDir | 0755,
+ },
+ },
+ safcm.MsgSyncResp{},
+ nil,
+ fmt.Errorf("symlink not permitted in path: \"dir\""),
+ },
+
+ // Border cases
+
+ {
+ "relative path with leading dot",
+ safcm.MsgSyncReq{},
+ &safcm.File{
+ Path: "./dir/file",
+ Mode: 0644,
+ Uid: -1,
+ Gid: -1,
+ OrigGroup: "group",
+ Data: []byte("content"),
+ },
+ func() {
+ ft.CreateDirectory("dir", 0755)
+ },
+ true,
+ []ft.File{
+ root,
+ {
+ Path: "dir",
+ Mode: fs.ModeDir | 0755,
+ },
+ {
+ Path: "dir/file",
+ Mode: 0644,
+ Data: []byte("content"),
+ },
+ },
+ safcm.MsgSyncResp{
+ FileChanges: []safcm.FileChange{
+ {
+ Path: "./dir/file",
+ Created: true,
+ New: safcm.FileChangeInfo{
+ Mode: 0644,
+ User: user,
+ Uid: uid,
+ Group: group,
+ Gid: gid,
+ },
+ },
+ },
+ },
+ []string{
+ `4: files: "./dir/file" (group): will create`,
+ `3: files: "./dir/file" (group): creating`,
+ `4: files: "./dir/file" (group): creating temporary file "dir/.file*"`,
+ `4: files: "./dir/file" (group): renaming "dir/.fileRND"`,
+ },
+ nil,
+ },
+
// Diffs
{