1 // Parse /etc/passwd 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 SerializePasswds()
34 const PasswdVersion = 1
46 // ParsePasswds parses a file in the format of /etc/passwd and returns all
47 // entries as slice of Passwd structs.
48 func ParsePasswds(r io.Reader) ([]Passwd, error) {
51 s := bufio.NewReader(r)
53 t, err := s.ReadString('\n')
61 x := strings.Split(t, ":")
63 return nil, fmt.Errorf("invalid line %q", t)
66 uid, err := strconv.ParseUint(x[2], 10, 64)
68 return nil, errors.Wrapf(err, "invalid uid in line %q", t)
70 gid, err := strconv.ParseUint(x[3], 10, 64)
72 return nil, errors.Wrapf(err, "invalid gid in line %q", t)
75 res = append(res, Passwd{
82 // ReadString() contains the delimiter
83 Shell: strings.TrimSuffix(x[6], "\n"),
89 func SerializePasswd(p Passwd) ([]byte, error) {
90 // Concatenate all (NUL-terminated) strings and store the offsets
92 data.Write([]byte(p.Name))
94 offPasswd := uint16(data.Len())
95 data.Write([]byte(p.Passwd))
97 offGecos := uint16(data.Len())
98 data.Write([]byte(p.Gecos))
100 offDir := uint16(data.Len())
101 data.Write([]byte(p.Dir))
103 offShell := uint16(data.Len())
104 data.Write([]byte(p.Shell))
106 size := uint16(data.Len())
108 var res bytes.Buffer // serialized result
109 le := binary.LittleEndian
111 id := make([]byte, 8)
113 le.PutUint64(id, p.Uid)
116 le.PutUint64(id, p.Gid)
119 off := make([]byte, 2)
121 le.PutUint16(off, offPasswd)
124 le.PutUint16(off, offGecos)
127 le.PutUint16(off, offDir)
130 le.PutUint16(off, offShell)
133 le.PutUint16(off, size)
136 res.Write(data.Bytes())
137 // We must pad each entry so that all uint64 at the beginning of the
138 // struct are 8 byte aligned
139 alignBufferTo(&res, 8)
141 return res.Bytes(), nil
144 func SerializePasswds(w io.Writer, pws []Passwd) error {
145 // Serialize passwords and store offsets
146 var data bytes.Buffer
147 offsets := make(map[Passwd]uint64)
148 for _, p := range pws {
149 // TODO: warn about duplicate entries
150 offsets[p] = uint64(data.Len())
151 x, err := SerializePasswd(p)
158 // Copy to prevent sorting from modifying the argument
159 sorted := make([]Passwd, len(pws))
162 le := binary.LittleEndian
163 tmp := make([]byte, 8)
165 // Create index "sorted" in input order, used when iterating over all
166 // passwd entries (getpwent_r); keeping the original order makes
168 var indexOrig bytes.Buffer
169 for _, p := range pws {
170 le.PutUint64(tmp, offsets[p])
174 // Create index sorted after id
175 var indexId bytes.Buffer
176 sort.Slice(sorted, func(i, j int) bool {
177 return sorted[i].Uid < sorted[j].Uid
179 for _, p := range sorted {
180 le.PutUint64(tmp, offsets[p])
184 // Create index sorted after name
185 var indexName bytes.Buffer
186 sort.Slice(sorted, func(i, j int) bool {
187 return sorted[i].Name < sorted[j].Name
189 for _, p := range sorted {
190 le.PutUint64(tmp, offsets[p])
195 if len(pws)*8 != indexOrig.Len() ||
196 indexOrig.Len() != indexId.Len() ||
197 indexId.Len() != indexName.Len() {
198 return fmt.Errorf("indexes have inconsistent length")
204 w.Write([]byte("NSS-CASH"))
206 le.PutUint64(tmp, PasswdVersion)
209 le.PutUint64(tmp, uint64(len(pws)))
213 le.PutUint64(tmp, offset)
216 offset += uint64(indexOrig.Len())
217 le.PutUint64(tmp, offset)
220 offset += uint64(indexId.Len())
221 le.PutUint64(tmp, offset)
224 offset += uint64(indexName.Len())
225 le.PutUint64(tmp, offset)
228 _, err := indexOrig.WriteTo(w)
232 _, err = indexId.WriteTo(w)
236 _, err = indexName.WriteTo(w)
240 _, err = data.WriteTo(w)