blob: 83f2a693955a8f2cb93bda87c91cbbac458cf0c8 (
plain) (
tree)
|
|
;;; 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: ((dash "1.5.0") (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.passwordstore.org/
;;; Code:
(require 'dash)
(require 'f)
(require 's)
(defvar password-store-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.
Nil arguments are ignored. Returns the output on success, or
outputs error message on failure."
(with-temp-buffer
(let ((exit-code
(apply 'call-process
(append
(list password-store-executable nil (current-buffer) nil)
(-reject 'null args)))))
(if (zerop exit-code)
(s-chomp (buffer-string))
(error (s-chomp (buffer-string)))))))
(defun password-store--run-init (gpg-ids &optional folder)
(apply 'password-store--run "init"
(if folder (format "--path=%s" folder))
gpg-ids))
(defun password-store--run-list (&optional subdir)
(error "Not implemented"))
(defun password-store--run-grep (&optional string)
(error "Not implemented"))
(defun password-store--run-find (&optional string)
(error "Not implemented"))
(defun password-store--run-show (entry)
(password-store--run "show"
entry))
(defun password-store--run-insert (entry password &optional force)
(error "Not implemented"))
(defun password-store--run-edit (entry)
(error "Not implemented"))
(defun password-store--run-generate (entry password-length &optional force no-symbols)
(password-store--run "generate"
(if force "--force")
(if no-symbols "--no-symbols")
entry
(number-to-string password-length)))
(defun password-store--run-remove (entry &optional recursive)
(password-store--run "remove"
"--force"
(if recursive "--recursive")
entry))
(defun password-store--run-rename (entry new-entry &optional force)
(password-store--run "rename"
(if force "--force")
entry
new-entry))
(defun password-store--run-copy (entry new-entry &optional force)
(password-store--run "copy"
(if force "--force")
entry
new-entry))
(defun password-store--run-git (&rest args)
(apply 'password-store--run "git"
args))
(defun password-store--run-version ()
(password-store--run "version"))
(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--completing-read ()
"Read a password entry in the minibuffer, with completion."
(completing-read "Password entry: " (password-store-list)))
(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 (password-store--completing-read)))
(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 (password-store--completing-read)))
(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-init (gpg-id)
"Initialize new password store and use GPG-ID for encryption.
Separate multiple IDs with spaces."
(interactive (list (read-string "GPG ID: ")))
(message (password-store--run-init (split-string gpg-id))))
;;;###autoload
(defun password-store-insert (entry password)
"Insert a new ENTRY containing PASSWORD."
(interactive (list (read-string "Password entry: ")
(read-passwd "Password: " t)))
(message (shell-command-to-string (format "echo %s | %s insert -m -f %s" password password-store-executable entry))))
;;;###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 entry password-length t)
nil)
;;;###autoload
(defun password-store-remove (entry)
"Remove existing password for ENTRY."
(interactive (list (password-store--completing-read)))
(message (password-store--run-remove entry t)))
;;;###autoload
(defun password-store-rename (entry new-entry)
"Rename ENTRY to NEW-ENTRY."
(interactive (list (password-store--completing-read)
(read-string "Rename entry to: ")))
(message (password-store--run-rename entry new-entry t)))
;;;###autoload
(defun password-store-version ()
"Show version of pass executable."
(interactive)
(message (password-store--run-version)))
;;;###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 (password-store--completing-read)))
(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"))))
(provide 'password-store)
;;; password-store.el ends here
|