Mercurial > go > multipass
comparison file/file.go @ 0:c18bc7b9d1d9
Basic binaries. checkpassword doesn't yet work.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Sat, 24 Oct 2015 21:32:03 -0400 |
parents | |
children | e58bfc7fc207 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:c18bc7b9d1d9 |
---|---|
1 // Package file handles I/O for single password files. | |
2 // | |
3 // A password file contains multiple passwords for a single user. | |
4 // It starts with a banner that indicates the version of the file, | |
5 // then has entries in the format specified by auth.Entry. | |
6 | |
7 package file | |
8 | |
9 import ( | |
10 "bufio" | |
11 "errors" | |
12 "os" | |
13 "os/user" | |
14 "syscall" | |
15 | |
16 "path" | |
17 | |
18 "golang.org/x/sys/unix" | |
19 "pfish.zone/go/multipass/auth" | |
20 ) | |
21 | |
22 const ( | |
23 // the Banner acts as a version indicator | |
24 Banner = "# Multipass v0.1 password file" | |
25 ) | |
26 | |
27 var ( | |
28 // Raised when | |
29 ErrorBadFile = errors.New("multipass/file: Invalid file format") | |
30 ) | |
31 | |
32 type ShadowFile struct { | |
33 name string | |
34 } | |
35 | |
36 // ForUser gets the given user's ShadowFile. | |
37 func ForUser(username string) (*ShadowFile, error) { | |
38 u, err := user.Lookup(username) | |
39 if err != nil { | |
40 return nil, err | |
41 } | |
42 return New(path.Join(u.HomeDir, MultipassFile)), nil | |
43 } | |
44 | |
45 // ForMe gets the current user's ShadowFile. | |
46 func ForMe() (*ShadowFile, error) { | |
47 u, err := user.Current() | |
48 if err != nil { | |
49 return nil, err | |
50 } | |
51 return New(path.Join(u.HomeDir, MultipassFile)), nil | |
52 } | |
53 | |
54 // New creates a ShadowFile for reading at the given path. | |
55 // If a file needs to be created, uses the given GID to create it. | |
56 func New(name string) *ShadowFile { | |
57 f := new(ShadowFile) | |
58 f.name = name | |
59 return f | |
60 } | |
61 | |
62 func (f *ShadowFile) newFilename() string { | |
63 return f.name + ".new" | |
64 } | |
65 | |
66 func (f *ShadowFile) open() (*os.File, *bufio.Scanner, error) { | |
67 file, err := os.Open(f.name) | |
68 if err != nil { | |
69 return nil, nil, err | |
70 } | |
71 scanner := bufio.NewScanner(file) | |
72 if !scanner.Scan() { | |
73 file.Close() | |
74 return nil, nil, err | |
75 } | |
76 if scanner.Text() != Banner { | |
77 file.Close() | |
78 return nil, nil, ErrorBadFile | |
79 } | |
80 return file, scanner, nil | |
81 } | |
82 | |
83 func (f *ShadowFile) Authenticate(password string) (bool, error) { | |
84 file, scanner, err := f.open() | |
85 if err != nil { | |
86 return false, err | |
87 } | |
88 defer file.Close() | |
89 | |
90 for scanner.Scan() { | |
91 entry, err := auth.EntryFromShadow(scanner.Text()) | |
92 // Skip invalid lines. | |
93 if err != nil { | |
94 continue | |
95 } | |
96 if entry.Authenticate(password) { | |
97 return true, nil | |
98 } | |
99 } | |
100 return false, nil | |
101 } | |
102 | |
103 func (f *ShadowFile) Add(entry *auth.Entry) error { | |
104 handle, err := f.openWrite() | |
105 if err != nil { | |
106 return err | |
107 } | |
108 err = handle.write(entry) | |
109 if err != nil { | |
110 handle.bail() | |
111 return err | |
112 } | |
113 for handle.next() { | |
114 if entry, err := handle.entry(); err == nil { | |
115 // If we get an invalid entry, just skip it. | |
116 if err := handle.write(entry); err != nil { | |
117 handle.bail() | |
118 return err | |
119 } | |
120 } | |
121 } | |
122 return handle.finalize() | |
123 } | |
124 | |
125 func (f *ShadowFile) openWrite() (*writeHandle, error) { | |
126 return openWriteHandle(f.newFilename(), f.name) | |
127 } | |
128 | |
129 type writeHandle struct { | |
130 // We write to a file handle pointing to tempName. | |
131 tempName string | |
132 tempFile *os.File | |
133 writer *bufio.Writer | |
134 | |
135 // It will eventually move to fileName, which is our source file. | |
136 fileName string | |
137 // If inFile is nil, we're creating a new multipass file. | |
138 inFile *os.File | |
139 scanner *bufio.Scanner | |
140 } | |
141 | |
142 func openWriteHandle(tempName, fileName string) (*writeHandle, error) { | |
143 h := new(writeHandle) | |
144 h.tempName = tempName | |
145 h.fileName = fileName | |
146 // Open the output file, readable only by the current user. | |
147 oldUmask := unix.Umask(077) | |
148 tempFile, err := os.Create(tempName) | |
149 unix.Umask(oldUmask) | |
150 if err != nil { | |
151 return nil, err | |
152 } | |
153 h.tempFile = tempFile | |
154 // Open the input file. | |
155 inFile, err := os.Open(fileName) | |
156 if err == nil { | |
157 // Prepare to read in the input file, if it exists. | |
158 h.inFile = inFile | |
159 h.scanner = bufio.NewScanner(h.inFile) | |
160 if !h.scanner.Scan() { | |
161 return nil, ErrorBadFile | |
162 } | |
163 if h.scanner.Text() != Banner { | |
164 return nil, ErrorBadFile | |
165 } | |
166 // Change the owner and group of the new file to that of the old. | |
167 inStat, err := h.inFile.Stat() | |
168 if err != nil { | |
169 h.bail() | |
170 return nil, err | |
171 } | |
172 inStatUnix := inStat.Sys().(*syscall.Stat_t) | |
173 if err := h.tempFile.Chown(int(inStatUnix.Uid), int(inStatUnix.Gid)); err != nil { | |
174 h.bail() | |
175 return nil, err | |
176 } | |
177 if err := h.tempFile.Chmod(inStat.Mode()); err != nil { | |
178 h.bail() | |
179 return nil, err | |
180 } | |
181 } | |
182 // TODO(pfish): If there is no input file, set the right permissions + group on the output file. | |
183 h.writer = bufio.NewWriter(h.tempFile) | |
184 if _, err := h.writer.WriteString(Banner + "\n"); err != nil { | |
185 return nil, err | |
186 } | |
187 return h, nil | |
188 } | |
189 | |
190 func (h *writeHandle) bail() { | |
191 h.tempFile.Close() | |
192 h.inFile.Close() | |
193 os.Remove(h.tempName) | |
194 } | |
195 | |
196 func (h *writeHandle) next() bool { | |
197 // If the scanner is nil, then we have no input file and therefore no next. | |
198 return h.scanner != nil && h.scanner.Scan() | |
199 } | |
200 | |
201 func (h *writeHandle) entry() (*auth.Entry, error) { | |
202 return auth.EntryFromShadow(h.scanner.Text()) | |
203 } | |
204 | |
205 func (h *writeHandle) write(entry *auth.Entry) error { | |
206 if _, err := h.writer.WriteString(entry.Encode()); err != nil { | |
207 return err | |
208 } | |
209 _, err := h.writer.WriteString("\n") | |
210 return err | |
211 } | |
212 | |
213 func (h *writeHandle) finalize() error { | |
214 h.inFile.Close() | |
215 // Make absolutely completely sure we're synced. | |
216 if err := h.writer.Flush(); err != nil { | |
217 h.tempFile.Close() | |
218 os.Remove(h.tempName) | |
219 return err | |
220 } | |
221 if err := h.tempFile.Sync(); err != nil { | |
222 h.tempFile.Close() | |
223 os.Remove(h.tempName) | |
224 return err | |
225 } | |
226 // Close the file. | |
227 if err := h.tempFile.Close(); err != nil { | |
228 os.Remove(h.tempName) | |
229 return err | |
230 } | |
231 // And atomically write it over the new one. | |
232 err := os.Rename(h.tempName, h.fileName) | |
233 if err != nil { | |
234 return err | |
235 } | |
236 // If we get here, everything succeeded, and only in this case | |
237 // will the multipass shadow file have been updated. | |
238 return nil | |
239 } |