-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
306 lines (265 loc) · 8.81 KB
/
main.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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
//go:generate goversioninfo -manifest=testdata/resource/goversioninfo.exe.manifest
package main
import (
"flag"
"fmt"
"os"
"strings"
"github.com/abiosoft/ishell"
"github.com/pedroalbanese/gostkp/internal/commands"
t "github.com/pedroalbanese/gostkp/internal/backend/types"
v1 "github.com/pedroalbanese/gostkp/internal/backend/keepassv1"
)
var (
keyFile = flag.String("key", "", "a key file to use to unlock the db")
dbFile = flag.String("db", "", "the db to open")
noninteractive = flag.String("n", "", "execute a given command and exit")
version = flag.Bool("version", false, "print version and exit")
)
const (
buildVersionString = "0.0.0"
)
// promptForDBPassword will determine the password based on environment vars or, lacking those, a prompt to the user
func promptForDBPassword(shell *ishell.Shell) (string) {
// we are prompting for the password
shell.Print("enter database password: ")
return shell.ReadPassword()
}
// newDB will create or open a DB with the parameters specified. `open` indicates whether the DB should be opened or not (vs created)
func newDB(dbPath string, password string, keyPath string, version int) (t.Database, error) {
var dbWrapper t.Database
dbWrapper = &v1.Database{}
dbOpts := t.Options {
DBPath: dbPath,
Password: password,
KeyPath: keyPath,
}
err := dbWrapper.Init(dbOpts)
return dbWrapper, err
}
func main() {
flag.Parse()
shell := ishell.New()
if *version {
shell.Printf("version: %s\n", buildVersionString)
os.Exit(1)
}
var dbWrapper t.Database
dbPath, exists := os.LookupEnv("KP_DATABASE")
if !exists {
dbPath = *dbFile
}
// default to the flag argument
keyPath := *keyFile
if envKeyfile, found := os.LookupEnv("KP_KEYFILE"); found && keyPath == "" {
keyPath = envKeyfile
}
for {
// if the password is coming from an environment variable, we need to terminate
// after the first attempt or it will fall into an infinite loop
var err error
password, passwordInEnv := os.LookupEnv("KP_PASSWORD")
if !passwordInEnv {
password = promptForDBPassword(shell)
if err != nil {
shell.Printf("could not retrieve password: %s", err)
os.Exit(1)
}
}
dbWrapper, err = newDB(dbPath, password, keyPath, 1)
if err != nil {
// typically, these errors will be a bad password, so we want to keep prompting until the user gives up
// if, however, the password is in an environment variable, we want to abort immediately so the program doesn't fall
// in to an infinite loop
shell.Printf("could not open database: %s\n", err)
if passwordInEnv {
os.Exit(1)
}
continue
}
break
}
// if dbWrapper.Locked() {
// shell.Printf("Lockfile exists for DB at path '%s', another process is using this database!\n", dbWrapper.SavePath())
// shell.Printf("Open anyways? Data loss may occur. (will only proceed if 'yes' is entered) ")
// line, err := shell.ReadLineErr()
// if err != nil {
// shell.Printf("could not read user input: %s\n", line)
// os.Exit(1)
// }
// if line != "yes" {
// shell.Println("aborting")
// os.Exit(1)
// }
// }
// if err := dbWrapper.Lock(); err != nil {
// shell.Printf("aborting, could not lock database: %s\n", err)
// os.Exit(1)
// }
// shell.Printf("opened database at %s\n", dbWrapper.SavePath())
shell.Set("db", dbWrapper)
shell.SetPrompt(fmt.Sprintf("/%s > ", dbWrapper.CurrentLocation().Name()))
shell.AddCmd(&ishell.Cmd{
Name: "ls",
Help: "ls [path]",
Func: commands.Ls(shell),
})
shell.AddCmd(&ishell.Cmd{
Name: "new",
Help: "new <path>",
LongHelp: "creates a new entry at <path>",
Func: commands.NewEntry(shell),
})
shell.AddCmd(&ishell.Cmd{
Name: "mkdir",
LongHelp: "create a new group",
Help: "mkdir <group name>",
Func: commands.NewGroup(shell),
})
shell.AddCmd(&ishell.Cmd{
Name: "saveas",
LongHelp: "save this db to a new file, existing credentials will be used, the new location will /not/ be used as the main save path",
Help: "saveas <file.kdb>",
Func: commands.SaveAs(shell),
})
if dbWrapper.Version() == t.V2 {
shell.AddCmd(&ishell.Cmd{
Name: "select",
Help: "select [-f] <entry>",
LongHelp: "shows details on a given value in an entry, passwords will be redacted unless '-f' is specified",
Func: commands.Select(shell),
})
}
shell.AddCmd(&ishell.Cmd{
Name: "show",
Help: "show [-f] <entry>",
LongHelp: "shows details on a given entry, passwords will be redacted unless '-f' is specified",
Func: commands.Show(shell),
})
shell.AddCmd(&ishell.Cmd{
Name: "cd",
Help: "cd <path>",
LongHelp: "changes the current group to a different path",
Func: commands.Cd(shell),
})
attachCmd := &ishell.Cmd{
Name: "attach",
LongHelp: "manages the attachment for a given entry",
Help: "attach <get|show|delete> <entry> <filesystem location>",
}
attachCmd.AddCmd(&ishell.Cmd{
Name: "create",
Help: "attach create <entry> <name> <filesystem location>",
LongHelp: "creates a new attachment based on a local file",
Func: commands.Attach(shell, "create"),
})
attachCmd.AddCmd(&ishell.Cmd{
Name: "get",
Help: "attach get <entry> <filesystem location> // ('-' for stdout)",
LongHelp: "retrieves an attachment and outputs it to a filesystem location",
Func: commands.Attach(shell, "get"),
})
attachCmd.AddCmd(&ishell.Cmd{
Name: "details",
Help: "attach details <entry>",
LongHelp: "shows the details of the attachment on an entry",
Func: commands.Attach(shell, "details"),
})
shell.AddCmd(attachCmd)
shell.AddCmd(&ishell.Cmd{
LongHelp: "searches for any entries with the regular expression '<term>' in their titles or contents",
Name: "search",
Help: "search <term>",
Func: commands.Search(shell),
})
shell.AddCmd(&ishell.Cmd{
Name: "rm",
Help: "rm <entry>",
LongHelp: "removes an entry",
Func: commands.Rm(shell),
})
shell.AddCmd(&ishell.Cmd{
Name: "xp",
Help: "xp <entry>",
LongHelp: "copies a password to the clipboard",
Func: commands.Xp(shell),
})
shell.AddCmd(&ishell.Cmd{
Name: "xk",
Help: "xk <entry>",
LongHelp: "copies a password to the clipboard",
Func: commands.Xk(shell),
})
shell.AddCmd(&ishell.Cmd{
Name: "edit",
Help: "edit <entry>",
LongHelp: "edits an existing entry",
Func: commands.Edit(shell),
})
shell.AddCmd(&ishell.Cmd{
Name: "pwd",
Help: "pwd",
LongHelp: "shows path of current group",
Func: commands.Pwd(shell),
})
shell.AddCmd(&ishell.Cmd{
Name: "save",
Help: "save",
LongHelp: "saves the database to its most recently used path",
Func: commands.Save(shell),
})
shell.AddCmd(&ishell.Cmd{
Name: "xx",
Help: "xx",
LongHelp: "clears the clipboard",
Func: commands.Xx(shell),
})
shell.AddCmd(&ishell.Cmd{
Name: "xu",
Help: "xu",
LongHelp: "copies username to the clipboard",
Func: commands.Xu(shell),
})
shell.AddCmd(&ishell.Cmd{
Name: "xw",
Help: "xw",
LongHelp: "copies url to clipboard",
Func: commands.Xw(shell),
})
shell.AddCmd(&ishell.Cmd{
Name: "mv",
Help: "mv <source> <destination>",
LongHelp: "moves entries between groups",
Func: commands.Mv(shell),
})
shell.AddCmd(&ishell.Cmd{
Name: "version",
Help: "version",
LongHelp: "prints version",
Func: func(c *ishell.Context) {
shell.Printf("version: %s\n", buildVersionString)
},
})
if *noninteractive != "" {
bits := strings.Split(*noninteractive, " ")
if err := shell.Process([]string{bits[0], strings.Join(bits[1:], " ")}...); err != nil {
shell.Printf("error processing command: %s\n", err)
}
} else {
shell.Run()
}
// This will run after the shell exits
fmt.Fprintln(os.Stderr, "exiting")
if dbWrapper.Changed() {
if err := commands.PromptAndSave(shell); err != nil {
fmt.Printf("error attempting to save database: %s\n", err)
os.Exit(1)
}
} else {
fmt.Fprintln(os.Stderr, "no changes detected since last save.")
}
// if err := dbWrapper.Unlock(); err != nil {
// fmt.Printf("failed to unlock db: %s", err)
// os.Exit(1)
// }
}