-// Helper copied to the remote hosts to run commands and deploy configuration
+// Helper copied to the remote host to run commands and deploy configuration
// Copyright (C) 2021 Simon Ruderich
//
if change.Old.Mode.Type() == fs.ModeSymlink {
// Some BSD systems permit changing permissions of
// symlinks but ignore them on traversal. To keep it
- // simple we don't support that and always use 0777 for
- // symlink permissions (the value on GNU/Linux).
+ // simple we don't support that and always use 0777
+ // for symlink permissions (the value on GNU/Linux).
//
// TODO: Add proper support for symlinks on BSD
change.Old.Mode |= 0777
}
}
- // Directory: create new directory (also type change to directory)
+ // Directory: create new directory, also type change to directory
if file.Mode.IsDir() && (change.Created || changeType) {
debugf("creating directory")
err := os.Mkdir(file.Path, 0700)
os.Remove(tmpPath)
return err
}
- // Permissions are irrelevant for symlinks
+ // Permissions are irrelevant for symlinks (on most systems)
default:
panic(fmt.Sprintf("invalid file type %s", file.Mode))
"ruderich.org/simon/safcm/cmd/safcm-remote/run"
)
+// testRunner implements run.Runner to test commands without actually running
+// them.
type testRunner struct {
t *testing.T
expCmds []*exec.Cmd
GroupRemoveSuffix = GroupSpecialSeparator + "remove"
)
-// Keep in sync with sync_info.go:infoGroupDetectedRegexp
+// Keep in sync with cmd/safcm/sync_info.go:infoGroupDetectedRegexp
var groupNameRegexp = regexp.MustCompile(`^[a-z0-9_-]+$`)
func LoadGroups(cfg *Config, hosts *Hosts) (map[string][]string, error) {
if x == GroupAll {
continue
}
+ // Don't validate against groupNameRegexp because
+ // hosts have less strict restrictions.
if strings.Contains(x, GroupSpecialSeparator) {
return nil, fmt.Errorf(
"%s member %q must not contain %q",
nameMap[x] = true
}
nameMatched := make(map[string]bool)
- // To detect typos we must check all given names but only want to add
- // each match once
+ // To detect typos we must check all given names but one host can be
+ // matched by multiple names (e.g. two groups with overlapping hosts)
hostMatched := make(map[string]bool)
var res []*config.Host
return hostInfoRespToGroups(resp), nil
}
-// Keep in sync with config/groups.go:groupNameRegexp
+// Keep in sync with cmd/safcm/config/groups.go:groupNameRegexp
var infoGroupDetectedRegexp = regexp.MustCompile(`[^a-z0-9_-]+`)
func hostInfoRespToGroups(resp safcm.MsgInfoResp) []string {
return nil, nil, err
}
- // Early entries have higher priorities
+ // Early entries in "group_priority" have higher priorities
groupPriority := make(map[string]int)
for i, x := range s.config.GroupPriority {
groupPriority[x] = len(s.config.GroupPriority) - i
var escapeRegexp = regexp.MustCompile(`[\x00-\x08\x0B-\x1F\x7F]`)
// EscapeControlCharacters escapes all ASCII control characters (except
-// newline and tab) by replacing them with their hex value.
+// newline and tab) by replacing them with their hex value. If the output is
+// to a TTY then the escaped characters are colored.
//
// This function must be used when displaying any input from remote hosts to
// prevent terminal escape code injections.
}
// NewConn creates a new connection. Events in the returned struct must be
-// regularly read or the connection will stall. This must be done before
+// regularly read or the connection will hang. This must be done before
// DialSSH is called to open a connection.
func NewConn(debug bool) *Conn {
ch := make(chan ConnEvent)
//
// The target directory must no permit other users to delete our files
// or symlink attacks and arbitrary code execution is possible. For
- // /tmp this is guaranteed by the sticky bit. Make sure it has the
- // proper permissions.
+ // /tmp this is guaranteed by the sticky bit. The code verifies the
+ // directory has the proper permissions.
//
// We cannot use `test -f && test -O` because this is open to TOCTOU
// attacks. `stat` gives use the full file state. If the file is owned
- // by us and not a symlink then it's safe to use (assuming sticky or
- // directory not writable by others).
+ // by us and not a symlink then it's safe to use (assuming sticky
+ // directory or directory not writable by others).
//
// `test -e` is only used to prevent error messages if the file
// doesn't exist. It does not guard against any races.
c.sshRemote,
fmt.Sprintf("cat > %q", path))...)
cmd.Stdin = bytes.NewReader(helper)
- err = c.handleStderrAsEvents(cmd)
+ err = c.handleStderrAsEvents(cmd) // cmd.Stderr
if err != nil {
return err
}