-
Notifications
You must be signed in to change notification settings - Fork 7
/
keepass-mode.el
173 lines (139 loc) · 6.15 KB
/
keepass-mode.el
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
;;; keepass-mode.el --- Mode to open Keepass DB -*- lexical-binding: t; coding: utf-8 -*-
;; Copyright (C) 2020 Ignasi Fosch
;; Author: Ignasi Fosch <[email protected]>
;; Keywords: data files tools
;; Version: 0.0.4
;; Homepage: https://github.com/ifosch/keepass-mode
;; Package-Requires: ((emacs "27"))
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses>.
;;; Commentary:
;; KeePass mode provides a major mode to work with KeePass DB files.
;; So far it provides with simple navigation through folders and entries,
;; and copying passwords to Emacs clipboard.
;;; Code:
(defvar-local keepass-mode-db "")
(defvar-local keepass-mode-password "")
(defvar-local keepass-mode-group-path "")
(defun keepass-mode-select ()
"Select an entry in current Keepass key."
(interactive)
(let ((entry (aref (tabulated-list-get-entry) 0)))
(if (keepass-mode-is-group-p entry)
(progn
(keepass-mode-update-group-path (keepass-mode-concat-group-path entry))
(keepass-mode-open))
(keepass-mode-show entry))))
(defun keepass-mode-back ()
"Navigate back in group tree."
(interactive)
(keepass-mode-update-group-path (replace-regexp-in-string "[^/]*/?$" "" keepass-mode-group-path))
(keepass-mode-open))
(defun keepass-mode-copy (field)
"Copy current entry FIELD to clipboard."
(let ((entry (aref (tabulated-list-get-entry) 0)))
(if (keepass-mode-is-group-p entry)
(message "%s is a group, not an entry" entry)
(progn (kill-new (keepass-mode-get field entry))
(message "%s for '%s%s' copied to kill-ring" field keepass-mode-group-path entry)))))
(defun keepass-mode-copy-url ()
"Copy current entry URL to clipboard."
(interactive)
(keepass-mode-copy "URL"))
(defun keepass-mode-copy-username ()
"Copy current entry username to clipboard."
(interactive)
(keepass-mode-copy "UserName"))
(defun keepass-mode-copy-password ()
"Copy current entry password to clipboard."
(interactive)
(keepass-mode-copy "Password"))
(defun keepass-mode-open ()
"Open a Keepass file at GROUP."
(let ((columns [("Key" 100)])
(rows (mapcar (lambda (x) `(nil [,x]))
(keepass-mode-get-entries keepass-mode-group-path))))
(setq tabulated-list-format columns)
(setq tabulated-list-entries rows)
(tabulated-list-init-header)
(tabulated-list-print)))
(defun keepass-mode-ask-password ()
"Ask the user for the password."
(read-passwd (format "Password for %s: " keepass-mode-db)))
(defun keepass-mode-show (group)
"Show a Keepass entry at GROUP."
(let* ((entry (keepass-mode-concat-group-path group))
(output (replace-regexp-in-string "Password: .+" "Password: *************" (keepass-mode-get-entry entry))))
(switch-to-buffer (format "*keepass %s %s*" keepass-mode-db entry))
(insert output)
(read-only-mode)))
(defvar keepass-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "RET") 'keepass-mode-select)
(define-key map (kbd "<backspace>") 'keepass-mode-back)
(define-key map (kbd "u") 'keepass-mode-copy-url)
(define-key map (kbd "b") 'keepass-mode-copy-username)
(define-key map (kbd "c") 'keepass-mode-copy-password)
map))
;;;###autoload
(define-derived-mode keepass-mode tabulated-list-mode "KeePass"
"KeePass mode for interacting with the KeePass DB. \\{keepass-mode-map}."
(setq-local keepass-mode-db buffer-file-truename)
(when (zerop (length keepass-mode-password))
(setq-local keepass-mode-password (keepass-mode-ask-password)))
(setq-local keepass-mode-group-path "")
(keepass-mode-open))
(add-to-list 'auto-mode-alist '("\\.kdbx\\'" . keepass-mode))
(add-to-list 'auto-mode-alist '("\\.kdb\\'" . keepass-mode))
(defun keepass-mode-get (field entry)
"Retrieve FIELD from ENTRY."
(keepass-mode-get-field field (shell-command-to-string (keepass-mode-command (keepass-mode-quote-unless-empty entry) "show -s"))))
(defun keepass-mode-get-entries (group)
"Get entry list for GROUP."
(nbutlast (split-string (shell-command-to-string (keepass-mode-command (keepass-mode-quote-unless-empty group) "ls")) "\n") 1))
(defun keepass-mode-concat-group-path (group)
"Concat GROUP and group path."
(format "%s%s" keepass-mode-group-path (or group "")))
(defun keepass-mode-update-group-path (group)
"Update group-path with GROUP."
(setq keepass-mode-group-path group))
(defun keepass-mode-get-entry (entry)
"Get ENTRY details."
(shell-command-to-string (keepass-mode-command (keepass-mode-quote-unless-empty entry) "show")))
(defun keepass-mode-get-field (field entry)
"Get FIELD from an ENTRY."
(keepass-mode-get-value-from-alist field (keepass-mode-read-data-from-string entry)))
(defun keepass-mode-command (group command)
"Generate KeePass COMMAND to run, on GROUP."
(format "echo %s | \
keepassxc-cli %s %s %s 2>&1 | \
egrep -v '[Insert|Enter] password to unlock %s'"
(shell-quote-argument keepass-mode-password)
command
keepass-mode-db
group
keepass-mode-db))
(defun keepass-mode-quote-unless-empty (text)
"Quote TEXT unless it's empty."
(if (= (length text) 0) text (format "'%s'" text)))
(defun keepass-mode-get-value-from-alist (key alist)
"Get the value for KEY from the ALIST."
(mapconcat 'identity (cdr (assoc key alist)) ":"))
(defun keepass-mode-read-data-from-string (input)
"Read data from INPUT string into an alist."
(mapcar
(lambda (arg) (split-string arg ":" nil " "))
(split-string input "\n")))
(defun keepass-mode-is-group-p (entry)
"Return if ENTRY is a group."
(string-suffix-p "/" entry))
(provide 'keepass-mode)
;;; keepass-mode.el ends here