1 // Parse /etc/group files and serialize them
3 // Copyright (C) 2019 Simon Ruderich
5 // This program is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU Affero General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU Affero General Public License for more details.
15 // You should have received a copy of the GNU Affero General Public License
16 // along with this program. If not, see <https://www.gnu.org/licenses/>.
30 "github.com/pkg/errors"
33 // Version written in SerializeGroups()
34 const GroupVersion = 1
43 // Go does not support slices in map keys; therefore, use this separate struct
44 type GroupKey struct {
51 func toKey(g Group) GroupKey {
56 Members: strings.Join(g.Members, ","),
60 // ParseGroups parses a file in the format of /etc/group and returns all
61 // entries as Group structs.
62 func ParseGroups(r io.Reader) ([]Group, error) {
65 s := bufio.NewScanner(r)
69 x := strings.Split(t, ":")
71 return nil, fmt.Errorf("invalid line %q", t)
74 gid, err := strconv.ParseUint(x[2], 10, 64)
76 return nil, errors.Wrapf(err, "invalid gid in line %q", t)
80 // No members must result in empty slice, not slice with the
83 members = strings.Split(x[3], ",")
85 res = append(res, Group{
100 func SerializeGroup(g Group) []byte {
101 le := binary.LittleEndian
103 // Concatenate all (NUL-terminated) strings and store the offsets
104 var mems bytes.Buffer
105 var mems_off []uint16
106 for _, m := range g.Members {
107 mems_off = append(mems_off, uint16(mems.Len()))
108 mems.Write([]byte(m))
111 var data bytes.Buffer
112 data.Write([]byte(g.Name))
114 offPasswd := uint16(data.Len())
115 data.Write([]byte(g.Passwd))
117 // Padding to align the following uint16
118 if data.Len()%2 != 0 {
121 offMemOff := uint16(data.Len())
122 // Offsets for group members
123 offMem := offMemOff + 2*uint16(len(mems_off))
124 for _, o := range mems_off {
125 tmp := make([]byte, 2)
126 le.PutUint16(tmp, offMem+o)
129 // And the group members concatenated as above
130 data.Write(mems.Bytes())
131 size := uint16(data.Len())
133 var res bytes.Buffer // serialized result
135 id := make([]byte, 8)
137 le.PutUint64(id, g.Gid)
140 off := make([]byte, 2)
142 le.PutUint16(off, offPasswd)
145 le.PutUint16(off, offMemOff)
148 le.PutUint16(off, uint16(len(g.Members)))
151 le.PutUint16(off, size)
154 res.Write(data.Bytes())
155 // We must pad each entry so that all uint64 at the beginning of the
156 // struct are 8 byte aligned
159 for i := 0; i < 8-l%8; i++ {
167 func SerializeGroups(w io.Writer, grs []Group) error {
168 // Serialize groups and store offsets
169 var data bytes.Buffer
170 offsets := make(map[GroupKey]uint64)
171 for _, g := range grs {
172 // TODO: warn about duplicate entries
173 offsets[toKey(g)] = uint64(data.Len())
174 data.Write(SerializeGroup(g))
177 // Copy to prevent sorting from modifying the argument
178 sorted := make([]Group, len(grs))
181 le := binary.LittleEndian
182 tmp := make([]byte, 8)
184 // Create index "sorted" in input order, used when iterating over all
185 // passwd entries (getgrent_r); keeping the original order makes
187 var indexOrig bytes.Buffer
188 for _, g := range grs {
189 le.PutUint64(tmp, offsets[toKey(g)])
193 // Create index sorted after id
194 var indexId bytes.Buffer
195 sort.Slice(sorted, func(i, j int) bool {
196 return sorted[i].Gid < sorted[j].Gid
198 for _, g := range sorted {
199 le.PutUint64(tmp, offsets[toKey(g)])
203 // Create index sorted after name
204 var indexName bytes.Buffer
205 sort.Slice(sorted, func(i, j int) bool {
206 return sorted[i].Name < sorted[j].Name
208 for _, g := range sorted {
209 le.PutUint64(tmp, offsets[toKey(g)])
214 if len(grs)*8 != indexOrig.Len() ||
215 indexOrig.Len() != indexId.Len() ||
216 indexId.Len() != indexName.Len() {
217 return fmt.Errorf("indexes have inconsistent length")
223 w.Write([]byte("NSS-CASH"))
225 le.PutUint64(tmp, GroupVersion)
228 le.PutUint64(tmp, uint64(len(grs)))
232 le.PutUint64(tmp, offset)
235 offset += uint64(indexOrig.Len())
236 le.PutUint64(tmp, offset)
239 offset += uint64(indexId.Len())
240 le.PutUint64(tmp, offset)
243 offset += uint64(indexName.Len())
244 le.PutUint64(tmp, offset)
247 _, err := indexOrig.WriteTo(w)
251 _, err = indexId.WriteTo(w)
255 _, err = indexName.WriteTo(w)
259 _, err = data.WriteTo(w)