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 }