Mercurial > go > multipass
view file/file.go @ 2:a4fa4f28b472
Actually shave off last character of password description.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Sat, 24 Oct 2015 22:34:07 -0400 |
parents | c18bc7b9d1d9 |
children | e58bfc7fc207 |
line wrap: on
line source
// Package file handles I/O for single password files. // // A password file contains multiple passwords for a single user. // It starts with a banner that indicates the version of the file, // then has entries in the format specified by auth.Entry. package file import ( "bufio" "errors" "os" "os/user" "syscall" "path" "golang.org/x/sys/unix" "pfish.zone/go/multipass/auth" ) const ( // the Banner acts as a version indicator Banner = "# Multipass v0.1 password file" ) var ( // Raised when ErrorBadFile = errors.New("multipass/file: Invalid file format") ) type ShadowFile struct { name string } // ForUser gets the given user's ShadowFile. func ForUser(username string) (*ShadowFile, error) { u, err := user.Lookup(username) if err != nil { return nil, err } return New(path.Join(u.HomeDir, MultipassFile)), nil } // ForMe gets the current user's ShadowFile. func ForMe() (*ShadowFile, error) { u, err := user.Current() if err != nil { return nil, err } return New(path.Join(u.HomeDir, MultipassFile)), nil } // New creates a ShadowFile for reading at the given path. // If a file needs to be created, uses the given GID to create it. func New(name string) *ShadowFile { f := new(ShadowFile) f.name = name return f } func (f *ShadowFile) newFilename() string { return f.name + ".new" } func (f *ShadowFile) open() (*os.File, *bufio.Scanner, error) { file, err := os.Open(f.name) if err != nil { return nil, nil, err } scanner := bufio.NewScanner(file) if !scanner.Scan() { file.Close() return nil, nil, err } if scanner.Text() != Banner { file.Close() return nil, nil, ErrorBadFile } return file, scanner, nil } func (f *ShadowFile) Authenticate(password string) (bool, error) { file, scanner, err := f.open() if err != nil { return false, err } defer file.Close() for scanner.Scan() { entry, err := auth.EntryFromShadow(scanner.Text()) // Skip invalid lines. if err != nil { continue } if entry.Authenticate(password) { return true, nil } } return false, nil } func (f *ShadowFile) Add(entry *auth.Entry) error { handle, err := f.openWrite() if err != nil { return err } err = handle.write(entry) if err != nil { handle.bail() return err } for handle.next() { if entry, err := handle.entry(); err == nil { // If we get an invalid entry, just skip it. if err := handle.write(entry); err != nil { handle.bail() return err } } } return handle.finalize() } func (f *ShadowFile) openWrite() (*writeHandle, error) { return openWriteHandle(f.newFilename(), f.name) } type writeHandle struct { // We write to a file handle pointing to tempName. tempName string tempFile *os.File writer *bufio.Writer // It will eventually move to fileName, which is our source file. fileName string // If inFile is nil, we're creating a new multipass file. inFile *os.File scanner *bufio.Scanner } func openWriteHandle(tempName, fileName string) (*writeHandle, error) { h := new(writeHandle) h.tempName = tempName h.fileName = fileName // Open the output file, readable only by the current user. oldUmask := unix.Umask(077) tempFile, err := os.Create(tempName) unix.Umask(oldUmask) if err != nil { return nil, err } h.tempFile = tempFile // Open the input file. inFile, err := os.Open(fileName) if err == nil { // Prepare to read in the input file, if it exists. h.inFile = inFile h.scanner = bufio.NewScanner(h.inFile) if !h.scanner.Scan() { return nil, ErrorBadFile } if h.scanner.Text() != Banner { return nil, ErrorBadFile } // Change the owner and group of the new file to that of the old. inStat, err := h.inFile.Stat() if err != nil { h.bail() return nil, err } inStatUnix := inStat.Sys().(*syscall.Stat_t) if err := h.tempFile.Chown(int(inStatUnix.Uid), int(inStatUnix.Gid)); err != nil { h.bail() return nil, err } if err := h.tempFile.Chmod(inStat.Mode()); err != nil { h.bail() return nil, err } } // TODO(pfish): If there is no input file, set the right permissions + group on the output file. h.writer = bufio.NewWriter(h.tempFile) if _, err := h.writer.WriteString(Banner + "\n"); err != nil { return nil, err } return h, nil } func (h *writeHandle) bail() { h.tempFile.Close() h.inFile.Close() os.Remove(h.tempName) } func (h *writeHandle) next() bool { // If the scanner is nil, then we have no input file and therefore no next. return h.scanner != nil && h.scanner.Scan() } func (h *writeHandle) entry() (*auth.Entry, error) { return auth.EntryFromShadow(h.scanner.Text()) } func (h *writeHandle) write(entry *auth.Entry) error { if _, err := h.writer.WriteString(entry.Encode()); err != nil { return err } _, err := h.writer.WriteString("\n") return err } func (h *writeHandle) finalize() error { h.inFile.Close() // Make absolutely completely sure we're synced. if err := h.writer.Flush(); err != nil { h.tempFile.Close() os.Remove(h.tempName) return err } if err := h.tempFile.Sync(); err != nil { h.tempFile.Close() os.Remove(h.tempName) return err } // Close the file. if err := h.tempFile.Close(); err != nil { os.Remove(h.tempName) return err } // And atomically write it over the new one. err := os.Rename(h.tempName, h.fileName) if err != nil { return err } // If we get here, everything succeeded, and only in this case // will the multipass shadow file have been updated. return nil }