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 Passwd structs.
48 func ParsePasswds(r io.Reader) ([]Passwd, error) {
51 s := bufio.NewScanner(r)
55 x := strings.Split(t, ":")
57 return nil, fmt.Errorf("invalid line %q", t)
60 uid, err := strconv.ParseUint(x[2], 10, 64)
62 return nil, errors.Wrapf(err, "invalid uid in line %q", t)
64 gid, err := strconv.ParseUint(x[3], 10, 64)
66 return nil, errors.Wrapf(err, "invalid gid in line %q", t)
69 res = append(res, Passwd{
87 func SerializePasswd(p Passwd) []byte {
88 // Concatenate all (NUL-terminated) strings and store the offsets
90 data.Write([]byte(p.Name))
92 offPasswd := uint16(data.Len())
93 data.Write([]byte(p.Passwd))
95 offGecos := uint16(data.Len())
96 data.Write([]byte(p.Gecos))
98 offDir := uint16(data.Len())
99 data.Write([]byte(p.Dir))
101 offShell := uint16(data.Len())
102 data.Write([]byte(p.Shell))
104 size := uint16(data.Len())
106 var res bytes.Buffer // serialized result
107 le := binary.LittleEndian
109 id := make([]byte, 8)
111 le.PutUint64(id, p.Uid)
114 le.PutUint64(id, p.Gid)
117 off := make([]byte, 2)
119 le.PutUint16(off, offPasswd)
122 le.PutUint16(off, offGecos)
125 le.PutUint16(off, offDir)
128 le.PutUint16(off, offShell)
131 le.PutUint16(off, size)
134 res.Write(data.Bytes())
135 // We must pad each entry so that all uint64 at the beginning of the
136 // struct are 8 byte aligned
139 for i := 0; i < 8-l%8; i++ {
147 func SerializePasswds(w io.Writer, pws []Passwd) error {
148 // Serialize passwords and store offsets
149 var data bytes.Buffer
150 offsets := make(map[Passwd]uint64)
151 for _, p := range pws {
152 // TODO: warn about duplicate entries
153 offsets[p] = uint64(data.Len())
154 data.Write(SerializePasswd(p))
157 // Copy to prevent sorting from modifying the argument
158 sorted := make([]Passwd, len(pws))
161 le := binary.LittleEndian
162 tmp := make([]byte, 8)
164 // Create index "sorted" in input order, used when iterating over all
165 // passwd entries (getpwent_r); keeping the original order makes
167 var indexOrig bytes.Buffer
168 for _, p := range pws {
169 le.PutUint64(tmp, offsets[p])
173 // Create index sorted after id
174 var indexId bytes.Buffer
175 sort.Slice(sorted, func(i, j int) bool {
176 return sorted[i].Uid < sorted[j].Uid
178 for _, p := range sorted {
179 le.PutUint64(tmp, offsets[p])
183 // Create index sorted after name
184 var indexName bytes.Buffer
185 sort.Slice(sorted, func(i, j int) bool {
186 return sorted[i].Name < sorted[j].Name
188 for _, p := range sorted {
189 le.PutUint64(tmp, offsets[p])
194 if len(pws)*8 != indexOrig.Len() ||
195 indexOrig.Len() != indexId.Len() ||
196 indexId.Len() != indexName.Len() {
197 return fmt.Errorf("indexes have inconsistent length")
203 w.Write([]byte("NSS-CASH"))
205 le.PutUint64(tmp, PasswdVersion)
208 le.PutUint64(tmp, uint64(len(pws)))
212 le.PutUint64(tmp, offset)
215 offset += uint64(indexOrig.Len())
216 le.PutUint64(tmp, offset)
219 offset += uint64(indexId.Len())
220 le.PutUint64(tmp, offset)
223 offset += uint64(indexName.Len())
224 le.PutUint64(tmp, offset)
227 _, err := indexOrig.WriteTo(w)
231 _, err = indexId.WriteTo(w)
235 _, err = indexName.WriteTo(w)
239 _, err = data.WriteTo(w)