]> ruderich.org/simon Gitweb - nsscash/nsscash.git/blob - group.go
nss: Makefile: don't link against asan
[nsscash/nsscash.git] / group.go
1 // Parse /etc/group files and serialize them
2
3 // Copyright (C) 2019  Simon Ruderich
4 //
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.
9 //
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.
14 //
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/>.
17
18 package main
19
20 import (
21         "bufio"
22         "bytes"
23         "encoding/binary"
24         "fmt"
25         "io"
26         "sort"
27         "strconv"
28         "strings"
29         "math"
30
31         "github.com/pkg/errors"
32 )
33
34 // Version written in SerializeGroups()
35 const GroupVersion = 1
36
37 type Group struct {
38         Name    string
39         Passwd  string
40         Gid     uint64
41         Members []string
42 }
43
44 // Go does not support slices in map keys; therefore, use this separate struct
45 type GroupKey struct {
46         Name    string
47         Passwd  string
48         Gid     uint64
49         Members string // "," separated
50 }
51
52 func toKey(g Group) GroupKey {
53         return GroupKey{
54                 Name:    g.Name,
55                 Passwd:  g.Passwd,
56                 Gid:     g.Gid,
57                 Members: strings.Join(g.Members, ","),
58         }
59 }
60
61 // ParseGroups parses a file in the format of /etc/group and returns all
62 // entries as slice of Group structs.
63 func ParseGroups(r io.Reader) ([]Group, error) {
64         var res []Group
65
66         s := bufio.NewReader(r)
67         for {
68                 t, err := s.ReadString('\n')
69                 if err != nil {
70                         if err == io.EOF {
71                                 break
72                         }
73                         return nil, err
74                 }
75
76                 x := strings.Split(t, ":")
77                 if len(x) != 4 {
78                         return nil, fmt.Errorf("invalid line %q", t)
79                 }
80
81                 gid, err := strconv.ParseUint(x[2], 10, 64)
82                 if err != nil {
83                         return nil, errors.Wrapf(err, "invalid gid in line %q", t)
84                 }
85
86                 // ReadString() contains the delimiter
87                 x[3] = strings.TrimSuffix(x[3], "\n")
88
89                 var members []string
90                 // No members must result in empty slice, not slice with the
91                 // empty string
92                 if x[3] != "" {
93                         members = strings.Split(x[3], ",")
94                 }
95                 res = append(res, Group{
96                         Name:    x[0],
97                         Passwd:  x[1],
98                         Gid:     gid,
99                         Members: members,
100                 })
101         }
102
103         return res, nil
104 }
105
106 func SerializeGroup(g Group) ([]byte, error) {
107         le := binary.LittleEndian
108
109         // Concatenate all (NUL-terminated) strings and store the offsets
110         var mems bytes.Buffer
111         var mems_off []uint16
112         for _, m := range g.Members {
113                 mems_off = append(mems_off, uint16(mems.Len()))
114                 mems.Write([]byte(m))
115                 mems.WriteByte(0)
116         }
117         var data bytes.Buffer
118         data.Write([]byte(g.Name))
119         data.WriteByte(0)
120         offPasswd := uint16(data.Len())
121         data.Write([]byte(g.Passwd))
122         data.WriteByte(0)
123         alignBufferTo(&data, 2) // align the following uint16
124         offMemOff := uint16(data.Len())
125         // Offsets for group members
126         offMem := offMemOff + 2*uint16(len(mems_off))
127         for _, o := range mems_off {
128                 tmp := make([]byte, 2)
129                 le.PutUint16(tmp, offMem+o)
130                 data.Write(tmp)
131         }
132         // And the group members concatenated as above
133         data.Write(mems.Bytes())
134         // Ensure the offsets can fit the length of this entry
135         if data.Len() > math.MaxUint16 {
136                 return nil, fmt.Errorf("group too large to serialize: %v, %v",
137                         data.Len(), g)
138         }
139         size := uint16(data.Len())
140
141         var res bytes.Buffer // serialized result
142
143         id := make([]byte, 8)
144         // gid
145         le.PutUint64(id, g.Gid)
146         res.Write(id)
147
148         off := make([]byte, 2)
149         // off_passwd
150         le.PutUint16(off, offPasswd)
151         res.Write(off)
152         // off_mem_off
153         le.PutUint16(off, offMemOff)
154         res.Write(off)
155         // mem_count
156         le.PutUint16(off, uint16(len(g.Members)))
157         res.Write(off)
158         // data_size
159         le.PutUint16(off, size)
160         res.Write(off)
161
162         res.Write(data.Bytes())
163         // We must pad each entry so that all uint64 at the beginning of the
164         // struct are 8 byte aligned
165         alignBufferTo(&res, 8)
166
167         return res.Bytes(), nil
168 }
169
170 func SerializeGroups(w io.Writer, grs []Group) error {
171         // Serialize groups and store offsets
172         var data bytes.Buffer
173         offsets := make(map[GroupKey]uint64)
174         for _, g := range grs {
175                 // TODO: warn about duplicate entries
176                 offsets[toKey(g)] = uint64(data.Len())
177                 x, err := SerializeGroup(g)
178                 if err != nil {
179                         return err
180                 }
181                 data.Write(x)
182         }
183
184         // Copy to prevent sorting from modifying the argument
185         sorted := make([]Group, len(grs))
186         copy(sorted, grs)
187
188         le := binary.LittleEndian
189         tmp := make([]byte, 8)
190
191         // Create index "sorted" in input order, used when iterating over all
192         // passwd entries (getgrent_r); keeping the original order makes
193         // debugging easier
194         var indexOrig bytes.Buffer
195         for _, g := range grs {
196                 le.PutUint64(tmp, offsets[toKey(g)])
197                 indexOrig.Write(tmp)
198         }
199
200         // Create index sorted after id
201         var indexId bytes.Buffer
202         sort.Slice(sorted, func(i, j int) bool {
203                 return sorted[i].Gid < sorted[j].Gid
204         })
205         for _, g := range sorted {
206                 le.PutUint64(tmp, offsets[toKey(g)])
207                 indexId.Write(tmp)
208         }
209
210         // Create index sorted after name
211         var indexName bytes.Buffer
212         sort.Slice(sorted, func(i, j int) bool {
213                 return sorted[i].Name < sorted[j].Name
214         })
215         for _, g := range sorted {
216                 le.PutUint64(tmp, offsets[toKey(g)])
217                 indexName.Write(tmp)
218         }
219
220         // Sanity check
221         if len(grs)*8 != indexOrig.Len() ||
222                 indexOrig.Len() != indexId.Len() ||
223                 indexId.Len() != indexName.Len() {
224                 return fmt.Errorf("indexes have inconsistent length")
225         }
226
227         // Write result
228
229         // magic
230         w.Write([]byte("NSS-CASH"))
231         // version
232         le.PutUint64(tmp, GroupVersion)
233         w.Write(tmp)
234         // count
235         le.PutUint64(tmp, uint64(len(grs)))
236         w.Write(tmp)
237         // off_orig_index
238         offset := uint64(0)
239         le.PutUint64(tmp, offset)
240         w.Write(tmp)
241         // off_id_index
242         offset += uint64(indexOrig.Len())
243         le.PutUint64(tmp, offset)
244         w.Write(tmp)
245         // off_name_index
246         offset += uint64(indexId.Len())
247         le.PutUint64(tmp, offset)
248         w.Write(tmp)
249         // off_data
250         offset += uint64(indexName.Len())
251         le.PutUint64(tmp, offset)
252         w.Write(tmp)
253
254         _, err := indexOrig.WriteTo(w)
255         if err != nil {
256                 return err
257         }
258         _, err = indexId.WriteTo(w)
259         if err != nil {
260                 return err
261         }
262         _, err = indexName.WriteTo(w)
263         if err != nil {
264                 return err
265         }
266         _, err = data.WriteTo(w)
267         if err != nil {
268                 return err
269         }
270
271         return nil
272 }