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')
62 x := strings.Split(t, ":")
64 return nil, fmt.Errorf("invalid line %q", t)
67 uid, err := strconv.ParseUint(x[2], 10, 64)
69 return nil, errors.Wrapf(err, "invalid uid in line %q", t)
71 gid, err := strconv.ParseUint(x[3], 10, 64)
73 return nil, errors.Wrapf(err, "invalid gid in line %q", t)
76 res = append(res, Passwd{
83 // ReadString() contains the delimiter
84 Shell: strings.TrimSuffix(x[6], "\n"),
90 func SerializePasswd(p Passwd) ([]byte, error) {
91 // Concatenate all (NUL-terminated) strings and store the offsets
93 data.Write([]byte(p.Name))
95 offPasswd := uint16(data.Len())
96 data.Write([]byte(p.Passwd))
98 offGecos := uint16(data.Len())
99 data.Write([]byte(p.Gecos))
101 offDir := uint16(data.Len())
102 data.Write([]byte(p.Dir))
104 offShell := uint16(data.Len())
105 data.Write([]byte(p.Shell))
107 // Ensure the offsets can fit the length of this entry
108 if data.Len() > math.MaxUint16 {
109 return nil, fmt.Errorf("passwd too large to serialize: %v, %v",
112 size := uint16(data.Len())
114 var res bytes.Buffer // serialized result
115 le := binary.LittleEndian
117 id := make([]byte, 8)
119 le.PutUint64(id, p.Uid)
122 le.PutUint64(id, p.Gid)
125 off := make([]byte, 2)
127 le.PutUint16(off, offPasswd)
130 le.PutUint16(off, offGecos)
133 le.PutUint16(off, offDir)
136 le.PutUint16(off, offShell)
139 le.PutUint16(off, size)
142 res.Write(data.Bytes())
143 // We must pad each entry so that all uint64 at the beginning of the
144 // struct are 8 byte aligned
145 alignBufferTo(&res, 8)
147 return res.Bytes(), nil
150 func SerializePasswds(w io.Writer, pws []Passwd) error {
151 // Serialize passwords and store offsets
152 var data bytes.Buffer
153 offsets := make(map[Passwd]uint64)
154 for _, p := range pws {
155 // TODO: warn about duplicate entries
156 offsets[p] = uint64(data.Len())
157 x, err := SerializePasswd(p)
164 // Copy to prevent sorting from modifying the argument
165 sorted := make([]Passwd, len(pws))
168 le := binary.LittleEndian
169 tmp := make([]byte, 8)
171 // Create index "sorted" in input order, used when iterating over all
172 // passwd entries (getpwent_r); keeping the original order makes
174 var indexOrig bytes.Buffer
175 for _, p := range pws {
176 le.PutUint64(tmp, offsets[p])
180 // Create index sorted after id
181 var indexId bytes.Buffer
182 sort.Slice(sorted, func(i, j int) bool {
183 return sorted[i].Uid < sorted[j].Uid
185 for _, p := range sorted {
186 le.PutUint64(tmp, offsets[p])
190 // Create index sorted after name
191 var indexName bytes.Buffer
192 sort.Slice(sorted, func(i, j int) bool {
193 return sorted[i].Name < sorted[j].Name
195 for _, p := range sorted {
196 le.PutUint64(tmp, offsets[p])
201 if len(pws)*8 != indexOrig.Len() ||
202 indexOrig.Len() != indexId.Len() ||
203 indexId.Len() != indexName.Len() {
204 return fmt.Errorf("indexes have inconsistent length")
210 w.Write([]byte("NSS-CASH"))
212 le.PutUint64(tmp, PasswdVersion)
215 le.PutUint64(tmp, uint64(len(pws)))
219 le.PutUint64(tmp, offset)
222 offset += uint64(indexOrig.Len())
223 le.PutUint64(tmp, offset)
226 offset += uint64(indexId.Len())
227 le.PutUint64(tmp, offset)
230 offset += uint64(indexName.Len())
231 le.PutUint64(tmp, offset)
234 _, err := indexOrig.WriteTo(w)
238 _, err = indexId.WriteTo(w)
242 _, err = indexName.WriteTo(w)
246 _, err = data.WriteTo(w)