summaryrefslogtreecommitdiff
path: root/contrib/emacs/password-store.el
blob: f95c9c7d537d26a0f482b876dbc06ce94bb614a5 (plain)
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
;;; password-store.el --- Password store (pass) support

;; Copyright (C) 2014 Svend Sorensen <svend@ciffer.net>

;; Author: Svend Sorensen <svend@ciffer.net>
;; Version: 0.1
;; Package-Requires: ((f "0.11.0") (s "1.9.0"))
;; Keywords: pass

;; This file is not part of GNU Emacs.

;; 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 <http://www.gnu.org/licenses/>.

;;; Commentary:

;; This package provides functions for working with pass ("the
;; standard Unix password manager").
;;
;; http://www.zx2c4.com/projects/password-store/

;;; Code:

(require 'f)
(require 's)

(defvar pass-executable
  (executable-find "pass")
  "Pass executable.")

(defconst password-store-password-length 8
  "Default password length.")

(defconst password-store-timeout 45
  "Number of seconds to wait before clearing the password.")

(defun password-store--run (&rest args)
  "Run pass with ARGS.

Returns the output on success, or outputs error message on
failure."
  (with-temp-buffer
    (let ((exit-code
	   (apply 'call-process
		  (append
		   (list pass-executable nil (current-buffer) nil)
		   args))))
      (if (zerop exit-code)
	  (buffer-string)
	(error (s-chomp (buffer-string)))))))

(defvar password-store-kill-ring-pointer nil
  "The tail of of the kill ring ring whose car is the password.")

(defun password-store-dir ()
  "Return password store directory."
  (or (getenv "PASSWORD_STORE_DIR")
      "~/.password-store"))

(defun password-store--entry-to-file (entry)
  "Return file name corresponding to ENTRY."
  (concat (f-join (password-store-dir) entry) ".gpg"))

(defun password-store--file-to-entry (file)
  "Return entry name corresponding to FILE."
  (f-no-ext (f-relative file (password-store-dir))))

(defun password-store-list (&optional subdir)
  "List password entries under SUBDIR."
  (unless subdir (setq subdir ""))
  (let ((dir (f-join (password-store-dir) subdir)))
    (if (f-directory? dir)
	(mapcar 'password-store--file-to-entry
		(f-files dir (lambda (file) (equal (f-ext file) "gpg")) t)))))

;;;###autoload
(defun password-store-edit (entry)
  "Edit password for ENTRY.

This edits the password file directly in Emacs, so changes will
need to be commited manually if git is being used."
  (interactive (list (completing-read "Password entry: " (password-store-list))))
  (find-file (password-store--entry-to-file entry)))

;;;###autoload
(defun password-store-get (entry)
  "Return password for ENTRY.

Returns the first line of the password data."
  (car (s-lines (password-store--run "show" entry))))

;;;###autoload
(defun password-store-clear ()
  "Clear password in kill ring."
  (interactive)
  (if password-store-kill-ring-pointer
      (progn
	(setcar password-store-kill-ring-pointer "")
	(setq password-store-kill-ring-pointer nil)
	(message "Password cleared."))))

;;;###autoload
(defun password-store-copy (entry)
  "Add password for ENTRY to kill ring.

Clear previous password from kill ring.  Pointer to kill ring is
stored in `password-store-kill-ring-pointer'.  Password is cleared
after `password-store-timeout' seconds."
  (interactive (list (completing-read "Password entry: " (password-store-list))))
  (let ((password (password-store-get entry)))
    (password-store-clear)
    (kill-new password)
    (setq password-store-kill-ring-pointer kill-ring-yank-pointer)
    (message "Copied %s to the kill ring. Will clear in %s seconds." entry password-store-timeout)
    (run-at-time password-store-timeout nil 'password-store-clear)))

;;;###autoload
(defun password-store-generate (entry &optional password-length)
  "Generate a new password for ENTRY with PASSWORD-LENGTH.

Default PASSWORD-LENGTH is `password-store-password-length'."
  (interactive (list (read-string "Password entry: ")
		     (when current-prefix-arg
		       (abs (prefix-numeric-value current-prefix-arg)))))
  (unless password-length (setq password-length password-store-password-length))
  ;; A message with the output of the command is not printed because
  ;; the output contains the password.
  (password-store--run "generate" "-f" entry (number-to-string password-length))
  nil)

;;;###autoload
(defun password-store-insert (entry password)
  "Insert a new ENTRY containing PASSWORD."
  (interactive (list (read-string "Password entry: ")
		     (read-passwd "Password: " t)))
  (message (s-chomp (shell-command-to-string (format "echo %s | %s insert -m -f %s" password pass-executable entry)))))

;;;###autoload
(defun password-store-remove (entry)
  "Remove existing password for ENTRY."
  (interactive (list (completing-read "Password entry: " (password-store-list))))
  (message (s-chomp (password-store--run "rm" "-f" entry))))

;;;###autoload
(defun password-store-url (entry)
  "Browse URL stored in ENTRY.

This will only browse URLs that start with http:// or http:// to
avoid sending a password to the browser."
  (interactive (list (completing-read "Password entry: " (password-store-list))))
  (let ((url (password-store-get entry)))
    (if (or (string-prefix-p "http://" url)
	    (string-prefix-p "https://" url))
	(browse-url url)
      (error "%s" "String does not look like a URL"))))

;;;###autoload
(defun password-store-version ()
  "Show version of pass executable."
  (interactive)
  (message (s-chomp (password-store--run "version"))))

;;; password-store.el ends here