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/>.
31 "github.com/pkg/errors"
34 // Version written in SerializePasswds()
35 const PasswdVersion = 1
47 // ParsePasswds parses a file in the format of /etc/passwd and returns all
48 // entries as slice of Passwd structs.
49 func ParsePasswds(r io.Reader) ([]Passwd, error) {
52 s := bufio.NewReader(r)
54 t, err := s.ReadString('\n')
58 return nil, fmt.Errorf(
59 "no newline in last line: %q",
67 x := strings.Split(t, ":")
69 return nil, fmt.Errorf("invalid line %q", t)
72 uid, err := strconv.ParseUint(x[2], 10, 64)
74 return nil, errors.Wrapf(err, "invalid uid in line %q", t)
76 gid, err := strconv.ParseUint(x[3], 10, 64)
78 return nil, errors.Wrapf(err, "invalid gid in line %q", t)
81 res = append(res, Passwd{
88 // ReadString() contains the delimiter
89 Shell: strings.TrimSuffix(x[6], "\n"),
95 func SerializePasswd(p Passwd) ([]byte, error) {
96 // Concatenate all (NUL-terminated) strings and store the offsets
98 data.Write([]byte(p.Name))
100 offPasswd := uint16(data.Len())
101 data.Write([]byte(p.Passwd))
103 offGecos := uint16(data.Len())
104 data.Write([]byte(p.Gecos))
106 offDir := uint16(data.Len())
107 data.Write([]byte(p.Dir))
109 offShell := uint16(data.Len())
110 data.Write([]byte(p.Shell))
112 // Ensure the offsets can fit the length of this entry
113 if data.Len() > math.MaxUint16 {
114 return nil, fmt.Errorf("passwd too large to serialize: %v, %v",
117 size := uint16(data.Len())
119 var res bytes.Buffer // serialized result
120 le := binary.LittleEndian
122 id := make([]byte, 8)
124 le.PutUint64(id, p.Uid)
127 le.PutUint64(id, p.Gid)
130 off := make([]byte, 2)
132 le.PutUint16(off, offPasswd)
135 le.PutUint16(off, offGecos)
138 le.PutUint16(off, offDir)
141 le.PutUint16(off, offShell)
144 le.PutUint16(off, size)
147 res.Write(data.Bytes())
148 // We must pad each entry so that all uint64 at the beginning of the
149 // struct are 8 byte aligned
150 alignBufferTo(&res, 8)
152 return res.Bytes(), nil
155 func SerializePasswds(w io.Writer, pws []Passwd) error {
156 // Serialize passwords and store offsets
157 var data bytes.Buffer
158 offsets := make(map[Passwd]uint64)
159 for _, p := range pws {
160 // TODO: warn about duplicate entries
161 offsets[p] = uint64(data.Len())
162 x, err := SerializePasswd(p)
169 // Copy to prevent sorting from modifying the argument
170 sorted := make([]Passwd, len(pws))
173 le := binary.LittleEndian
174 tmp := make([]byte, 8)
176 // Create index "sorted" in input order, used when iterating over all
177 // passwd entries (getpwent_r); keeping the original order makes
179 var indexOrig bytes.Buffer
180 for _, p := range pws {
181 le.PutUint64(tmp, offsets[p])
185 // Create index sorted after id
186 var indexId bytes.Buffer
187 sort.Slice(sorted, func(i, j int) bool {
188 return sorted[i].Uid < sorted[j].Uid
190 for _, p := range sorted {
191 le.PutUint64(tmp, offsets[p])
195 // Create index sorted after name
196 var indexName bytes.Buffer
197 sort.Slice(sorted, func(i, j int) bool {
198 return sorted[i].Name < sorted[j].Name
200 for _, p := range sorted {
201 le.PutUint64(tmp, offsets[p])
206 if len(pws)*8 != indexOrig.Len() ||
207 indexOrig.Len() != indexId.Len() ||
208 indexId.Len() != indexName.Len() {
209 return fmt.Errorf("indexes have inconsistent length")
215 w.Write([]byte("NSS-CASH"))
217 le.PutUint64(tmp, PasswdVersion)
220 le.PutUint64(tmp, uint64(len(pws)))
224 le.PutUint64(tmp, offset)
227 offset += uint64(indexOrig.Len())
228 le.PutUint64(tmp, offset)
231 offset += uint64(indexId.Len())
232 le.PutUint64(tmp, offset)
235 offset += uint64(indexName.Len())
236 le.PutUint64(tmp, offset)
239 _, err := indexOrig.WriteTo(w)
243 _, err = indexId.WriteTo(w)
247 _, err = indexName.WriteTo(w)
251 _, err = data.WriteTo(w)