forked from gopasspw/gopass
-
Notifications
You must be signed in to change notification settings - Fork 0
/
edit.go
110 lines (94 loc) · 2.91 KB
/
edit.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package action
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"github.com/justwatchcom/gopass/fsutil"
"github.com/justwatchcom/gopass/password"
"github.com/justwatchcom/gopass/pwgen"
"github.com/justwatchcom/gopass/tpl"
shellquote "github.com/kballard/go-shellquote"
"github.com/urfave/cli"
)
// Edit the content of a password file
func (s *Action) Edit(c *cli.Context) error {
name := c.Args().First()
if name == "" {
return fmt.Errorf("provide a secret name")
}
exists, err := s.Store.Exists(name)
if err != nil && err != password.ErrNotFound {
return fmt.Errorf("failed to see if %s exists", name)
}
var content []byte
var changed bool
if exists {
content, err = s.Store.Get(name)
if err != nil {
return fmt.Errorf("failed to decrypt %s: %v", name, err)
}
} else if tmpl, found := s.Store.LookupTemplate(name); found {
changed = true
// load template if it exists
content = pwgen.GeneratePassword(defaultLength, false)
if nc, err := tpl.Execute(string(tmpl), name, content, s.Store); err == nil {
content = nc
} else {
fmt.Printf("failed to execute template: %s\n", err)
}
}
nContent, err := s.editor(content)
if err != nil {
return err
}
// If content is equal, nothing changed, exiting
if bytes.Equal(content, nContent) && !changed {
return nil
}
return s.Store.SetConfirm(name, nContent, fmt.Sprintf("Edited with %s", os.Getenv("EDITOR")), s.confirmRecipients)
}
func (s *Action) editor(content []byte) ([]byte, error) {
editor := os.Getenv("EDITOR")
if editor == "" {
editor = "editor"
}
tmpfile, err := ioutil.TempFile(fsutil.Tempdir(), "gopass-edit")
if err != nil {
return []byte{}, fmt.Errorf("failed to create tmpfile to start with %s: %v", editor, tmpfile.Name())
}
defer func() {
if err := os.Remove(tmpfile.Name()); err != nil {
log.Fatal(err)
}
}()
if _, err := tmpfile.Write([]byte(content)); err != nil {
return []byte{}, fmt.Errorf("failed to create tmpfile to start with %s: %v", editor, tmpfile.Name())
}
if err := tmpfile.Close(); err != nil {
return []byte{}, fmt.Errorf("failed to create tmpfile to start with %s: %v", editor, tmpfile.Name())
}
cmdArgs, err := shellquote.Split(editor)
if err != nil {
return []byte{}, fmt.Errorf("failed to parse EDITOR command `%s`", editor)
}
editor = cmdArgs[0]
args := append(cmdArgs[1:], tmpfile.Name())
cmd := exec.Command(editor, args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return []byte{}, fmt.Errorf("failed to run %s with %s file", editor, tmpfile.Name())
}
nContent, err := ioutil.ReadFile(tmpfile.Name())
if err != nil {
return []byte{}, fmt.Errorf("failed to read from tmpfile: %v", err)
}
// enforce unix line endings in the password store
nContent = bytes.Replace(nContent, []byte("\r\n"), []byte("\n"), -1)
nContent = bytes.Replace(nContent, []byte("\r"), []byte("\n"), -1)
return nContent, nil
}