// Mgmt // Copyright (C) James Shubin and the project contributors // Written by James Shubin and the project contributors // // 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 . // // Additional permission under GNU GPL version 3 section 7 // // If you modify this program, or any covered work, by linking or combining it // with embedded mcl code and modules (and that the embedded mcl code and // modules which link with this program, contain a copy of their source code in // the authoritative form) containing parts covered by the terms of any other // license, the licensors of this program grant you additional permission to // convey the resulting work. Furthermore, the licensors of this program grant // the original author, James Shubin, additional permission to update this // additional permission if he deems it necessary to achieve the goals of this // additional permission. package util import ( "fmt" "os" "strings" ) // Constant bytes for the who (u, g, or o) and the what (r, w, x, s, or t). const ( ModeUser uint32 = 64 ModeGroup uint32 = 8 ModeOther uint32 = 1 ModeRead uint32 = 4 ModeWrite uint32 = 2 ModeExec uint32 = 1 ModeSetU uint32 = 4 ModeSetG uint32 = 2 ModeSticky uint32 = 1 ) // modeIsValidWho checks that only the characters 'u', 'g', 'o' are in the who // string. It expects that 'a' was expanded to "ugo" already and will return // false if not. func modeIsValidWho(who string) bool { for _, w := range []string{"u", "g", "o"} { who = strings.ReplaceAll(who, w, "") } return len(who) == 0 } // modeIsValidWhat checks that only the valid mode characters are in the what // string ('r', 'w', 'x', 's', 't'). func modeIsValidWhat(what string) bool { for _, w := range []string{"r", "w", "x", "s", "t"} { what = strings.ReplaceAll(what, w, "") } return len(what) == 0 } // modeAssigned executes an assignment symbolic mode string (u=r). It clears out // any bits for every subject in who and then assigns the specified modes in // what. func modeAssigned(who, what string, from os.FileMode) (os.FileMode, error) { // Clear out any users defined in 'who'. for _, w := range who { switch w { case 'u': from = from &^ os.FileMode(448) // 111000000 in bytes from = from &^ os.ModeSetuid case 'g': from = from &^ os.FileMode(56) // 111000 in bytes from = from &^ os.ModeSetgid case 'o': from = from &^ os.FileMode(7) // 111 in bytes } } for _, c := range what { switch c { case 'r': m := modeValueFrom(who, ModeRead) if from&m == 0 { from = from | m } case 'w': m := modeValueFrom(who, ModeWrite) if from&m == 0 { from = from | m } case 'x': m := modeValueFrom(who, ModeExec) if from&m == 0 { from = from | m } case 's': for _, w := range who { switch w { case 'u': from = from | os.ModeSetuid case 'g': from = from | os.ModeSetgid } } case 't': from = from | os.ModeSticky default: return os.FileMode(0), fmt.Errorf("invalid character %q", c) } } return from, nil } // modeAdded executes an addition symbolic mode string (u+x) and will add the // bits requested in what if not present. func modeAdded(who, what string, from os.FileMode) (os.FileMode, error) { for _, c := range what { switch c { case 'r': m := modeValueFrom(who, ModeRead) if from&m == 0 { from = from | m } case 'w': m := modeValueFrom(who, ModeWrite) if from&m == 0 { from = from | m } case 'x': m := modeValueFrom(who, ModeExec) if from&m == 0 { from = from | m } case 's': for _, w := range who { switch w { case 'u': from = from | os.ModeSetuid case 'g': from = from | os.ModeSetgid } } case 't': from = from | os.ModeSticky default: return os.FileMode(0), fmt.Errorf("invalid character %q", c) } } return from, nil } // modeSubtracted executes an subtraction symbolic mode string (u+x) and will // removethe bits requested in what if present. func modeSubtracted(who, what string, from os.FileMode) (os.FileMode, error) { for _, c := range what { switch c { case 'r': m := modeValueFrom(who, ModeRead) if from&m != 0 { from = from &^ m } case 'w': m := modeValueFrom(who, ModeWrite) if from&m != 0 { from = from &^ m } case 'x': m := modeValueFrom(who, ModeExec) if from&m != 0 { from = from &^ m } case 's': for _, w := range who { switch w { case 'u': if from&os.ModeSetuid != 0 { from = from &^ os.ModeSetuid } case 'g': if from&os.ModeSetgid != 0 { from = from &^ os.ModeSetgid } } } case 't': if from&os.ModeSticky != 0 { from = from | os.ModeSticky } default: return os.FileMode(0), fmt.Errorf("invalid character %q", c) } } return from, nil } // modeValueFrom will return the bits requested for the mode in the correct // positions for the specified subjects in who. func modeValueFrom(who string, modeType uint32) os.FileMode { i := uint32(0) for _, w := range who { switch w { case 'u': i += ModeUser * uint32(modeType) case 'g': i += ModeGroup * uint32(modeType) case 'o': i += ModeOther * uint32(modeType) } } return os.FileMode(i) } // ParseSymbolicModes parses a slice of symbolic mode strings. By default it // will only accept the assignment input (=), but if allowAssign is true, then // all symbolic mode strings (=, +, -) can be used as well. // // Symbolic mode is expected to be a string of who (user, group, other) then the // operation (=, +, -) then the change (read, write, execute, setuid, setgid, // sticky). // // Eg: ug=rw // // If you repeat yourself in the slice (eg. u=rw,u=w) ParseSymbolicModes will // fail with an error. func ParseSymbolicModes(modes []string, from os.FileMode, allowAssign bool) (os.FileMode, error) { symModes := make([]struct { mode, who, what string parse func(who, what string, from os.FileMode) (os.FileMode, error) }, len(modes)) for i, mode := range modes { symModes[i].mode = mode // If string contains '=' and no '+/-' it is safe to guess it is // an assign. if strings.Contains(mode, "=") && !strings.ContainsAny(mode, "+-") { m := strings.Split(mode, "=") if len(m) != 2 { return os.FileMode(0), fmt.Errorf("only a single %q is allowed but found %d", "=", len(m)) } symModes[i].who = m[0] symModes[i].what = m[1] symModes[i].parse = modeAssigned continue } else if strings.Contains(mode, "+") && !strings.ContainsAny(mode, "=-") && allowAssign { m := strings.Split(mode, "+") if len(m) != 2 { return os.FileMode(0), fmt.Errorf("only a single %q is allowed but found %d", "+", len(m)) } symModes[i].who = m[0] symModes[i].what = m[1] symModes[i].parse = modeAdded continue } else if strings.Contains(mode, "-") && !strings.ContainsAny(mode, "=+") && allowAssign { m := strings.Split(mode, "-") if len(m) != 2 { return os.FileMode(0), fmt.Errorf("only a single %q is allowed but found %d", "-", len(m)) } symModes[i].who = m[0] symModes[i].what = m[1] symModes[i].parse = modeSubtracted continue } return os.FileMode(0), fmt.Errorf("%s is not a valid a symbolic mode", symModes[i].mode) } // Validate input and verify the slice of symbolic modes does not // contain redundancy. seen := make(map[rune]struct{}) // validate that subjects are not duplicated for i := range symModes { if strings.ContainsRune(symModes[i].who, 'a') || symModes[i].who == "" { // If 'a' or empty who (implicit 'a') is called and // there are more symbolic modes in the slice then it // must be a repetition. if len(symModes) > 1 { return os.FileMode(0), fmt.Errorf("subject was repeated: each subject (u,g,o) is only accepted once") } symModes[i].who = "ugo" } if !modeIsValidWhat(symModes[i].what) || !modeIsValidWho(symModes[i].who) { return os.FileMode(0), fmt.Errorf("unexpected character assignment in %s", symModes[i].mode) } for _, w := range symModes[i].who { if _, ok := seen[w]; ok { return os.FileMode(0), fmt.Errorf("subject was repeated: only define each subject (u,g,o) once") } seen[w] = struct{}{} } } // Parse each symbolic mode accumulatively onto the file mode. for _, m := range symModes { var err error from, err = m.parse(m.who, m.what, from) if err != nil { return os.FileMode(0), err } } return from, nil }