diff --git a/contrib/lisp/org-passwords.el b/contrib/lisp/org-passwords.el new file mode 100644 index 000000000..7ed8c8029 --- /dev/null +++ b/contrib/lisp/org-passwords.el @@ -0,0 +1,342 @@ +;;; org-passwords.el --- org derived mode for managing passwords + +;; Author: Jorge A. Alfaro-Murillo +;; Created: December 26, 2012 +;; Keywords: passwords, password + +;; 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 GNU Emacs. If not, see . +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;;; Commentary: + +;; This file contains the code for managing your passwords with +;; Org-mode. + +;; A basic setup needs to indicate a passwords file, and a dictionary +;; for the random words: + +;; (require org-passwords) +;; (setq org-passwords-file "~/documents/passwords.gpg") +;; (setq org-passwords-random-words-dictionary "/etc/dictionaries-common/words") + +;; Basic usage: + +;; `M-x org-passwords' opens the passwords file in +;; `org-passwords-mode'. + +;; `M-x org-passwords-generate-password' generates a random string +;; of numbers, lowercase letters and uppercase letters. + +;; `C-u M-x org-passwords-generate-password' generates a random +;; string of numbers, lowercase letters, uppercase letters and +;; symbols. + +;; `M-x org-passwords-random-words' concatenates random words from +;; the dictionary defined by `org-passwords-random-words-dictionary' +;; into a string, each word separated by the string defined in +;; `org-passwords-random-words-separator'. + +;; `C-u M-x org-passwords-random-words' does the same as above, and +;; also makes substitutions according to +;; `org-passwords-random-words-substitutions'. + +;; It is also useful to set up keybindings for the functions +;; `org-passwords-copy-username' and +;; `org-passwords-copy-password' in the +;; `org-passwords-mode', to easily make the passwords and usernames +;; available to the facility for pasting text of the window system +;; (clipboard on X and MS-Windows, pasteboard on Nextstep/Mac OS, +;; etc.), without inserting them in the kill-ring. You can set for +;; example: + +;; (eval-after-load "org-passwords" +;; '(progn +;; (define-key org-passwords-mode-map +;; (kbd "C-c u") +;; 'org-passwords-copy-username) +;; (define-key org-passwords-mode-map +;; (kbd "C-c p") +;; 'org-passwords-copy-password))) + +;; Finally, to enter new passwords, you can use `org-capture' and a minimal template like: + +;; ("p" "password" entry (file "~/documents/passwords.gpg") +;; "* %^{Title}\n %^{PASSWORD}p %^{USERNAME}p") + +;; When asked for the password you can then call either +;; `org-passwords-generate-password' or `org-passwords-random-words'. +;; Be sure to enable recursive minibuffers to call those functions +;; from the minibuffer: + +;; (setq enable-recursive-minibuffers t) + +;;; Code: + +(require 'org) + +(define-derived-mode org-passwords-mode org-mode + "org-passwords-mode" + "Mode for storing passwords" + nil) + +(defgroup org-passwords nil + "Options for password management." + :group 'org) + +(defcustom org-passwords-password-property "PASSWORD" + "Name of the property for password entry password." + :type 'string + :group 'org-passwords) + +(defcustom org-passwords-username-property "USERNAME" + "Name of the property for password entry user name." + :type 'string + :group 'org-passwords) + +(defcustom org-passwords-file nil + "Default file name for the file that contains the passwords." + :type 'file + :group 'org-passwords) + +(defcustom org-passwords-time-opened "1 min" + "Time that the password file will remain open. It has to be a +string, a number followed by units." + :type 'str + :group 'org-passwords) + +(defcustom org-passwords-random-words-dictionary nil + "Default file name for the file that contains a dictionary of +words for `org-passwords-random-words'. Each non-empty line in +the file is considered a word." + :type 'file + :group 'org-passwords) + +(defvar org-passwords-random-words-separator "-" + "A string to separate words in `org-passwords-random-words'.") + +(defvar org-passwords-random-words-substitutions + '(("a" . "@") + ("e" . "3") + ("o" . "0")) +"A list of substitutions to be made with +`org-passwords-random-words' if it is called with +`universal-argument'. Each element is pair of +strings (SUBSTITUTE-THIS . BY-THIS).") + +(defun org-passwords-copy-password () + "Makes the password available to other programs. Puts the +password of the entry at the location of the cursor in the +facility for pasting text of the window system (clipboard on X +and MS-Windows, pasteboard on Nextstep/Mac OS, etc.), without +putting it in the kill ring." + (interactive) + (save-excursion + (search-backward-regexp "^\\*") + (search-forward-regexp (concat "^[[:space:]]*:" + org-passwords-password-property + ":[[:space:]]*")) + (funcall interprogram-cut-function + (buffer-substring-no-properties (point) + (funcall (lambda () + (end-of-line) + (point))))))) + +(defun org-passwords-copy-username () + "Makes the password available to other programs. Puts the +username of the entry at the location of the cursor in the +facility for pasting text of the window system (clipboard on X +and MS-Windows, pasteboard on Nextstep/Mac OS, etc.), without +putting it in the kill ring." + (interactive) + (save-excursion + (search-backward-regexp "^\\*") + (search-forward-regexp (concat "^[[:space:]]*:" + org-passwords-username-property + ":[[:space:]]*")) + (funcall interprogram-cut-function + (buffer-substring-no-properties (point) + (funcall (lambda () + (end-of-line) + (point))))))) + +(defun org-passwords () + "Open the password file. Open the password file defined by the +variable `org-password-file' in read-only mode and kill that +buffer later according to the value of the variable +`org-passwords-time-opened'. It also adds the `org-password-file' +to the auto-mode-alist so that it is opened with its mode being +`org-passwords-mode'." + (interactive) + (if org-passwords-file + (progn + (add-to-list 'auto-mode-alist + (cons + (regexp-quote + (expand-file-name org-passwords-file)) + 'org-passwords-mode)) + (find-file-read-only org-passwords-file) + (org-passwords-set-up-kill-password-buffer)) + (minibuffer-message "No default password file defined. Set the variable `org-password-file'."))) + +(defun org-passwords-set-up-kill-password-buffer () + (run-at-time org-passwords-time-opened + nil + '(lambda () + (if (get-file-buffer org-passwords-file) + (kill-buffer + (get-file-buffer org-passwords-file)))))) + +;;; Password generator + +;; Set random number seed from current time and pid. Otherwise +;; `random' gives the same results every time emacs restarts. +(random t) + +(defun org-passwords-generate-password (arg) + "Ask a number of characters and insert a password of that size. +Password has a random string of numbers, lowercase letters, and +uppercase letters. Argument ARG include symbols." + (interactive "P") + (let ((number-of-chars + (string-to-number + (read-from-minibuffer "Number of Characters: ")))) + (if arg + (insert (org-passwords-generate-password-with-symbols "" number-of-chars)) + (insert (org-passwords-generate-password-without-symbols "" number-of-chars))))) + +(defun org-passwords-generate-password-with-symbols (previous-string nums-of-chars) + "Return a string consisting of PREVIOUS-STRING and +NUMS-OF-CHARS random characters." + (if (eq nums-of-chars 0) previous-string + (insert (org-passwords-generate-password-with-symbols + (concat previous-string + (char-to-string + ;; symbols, letters, numbers are from 33 to 126 + (+ (random (- 127 33)) 33))) + (1- nums-of-chars))))) + +(defun org-passwords-generate-password-without-symbols (previous-string nums-of-chars) + "Return string consisting of PREVIOUS-STRING and NUMS-OF-CHARS +random numbers, lowercase letters, and numbers." + (if (eq nums-of-chars 0) + previous-string + ; There are 10 numbers, 26 lowercase letters and 26 uppercase + ; letters. 10 + 26 + 26 = 62. The number characters go from 48 + ; to 57, the uppercase letters from 65 to 90, and the lowercase + ; from 97 to 122. The following makes each equally likely. + (let ((temp-value (random 62))) + (cond ((< temp-value 10) + ; If temp-value<10, then add a number + (org-passwords-generate-password-without-symbols + (concat previous-string + (char-to-string (+ 48 temp-value))) + (1- nums-of-chars))) + ((and (> temp-value 9) (< temp-value 36)) + ; If 9 temp-value 35) + ; If temp-value>35, then add a lowecase letter + (org-passwords-generate-password-without-symbols + (concat previous-string + (char-to-string (+ 97 (- temp-value 36)))) + (1- nums-of-chars))))))) + +;;; Random words + +(defun org-passwords-random-words (arg) + "Ask for a number of words and inserts a sequence of that many +random words from the list in the file +`org-passwords-random-words-dictionary' separated by +`org-passwords-random-words-separator'. ARG make substitutions in +the words as defined by +`org-passwords-random-words-substitutions'." + (interactive "P") + (if org-passwords-random-words-dictionary + (let ((number-of-words + (string-to-number + (read-from-minibuffer "Number of words: "))) + (list-of-words + (with-temp-buffer + (insert-file-contents + org-passwords-random-words-dictionary) + (split-string (buffer-string) "\n" t)))) + (insert + (org-passwords-substitute + (org-passwords-random-words-attach-number-of-words + (nth (random (length list-of-words)) + list-of-words) + (1- number-of-words) + list-of-words + org-passwords-random-words-separator) + (if arg + org-passwords-random-words-substitutions + nil)))) + (minibuffer-message + "No default dictionary file defined. Set the variable `org-passwords-random-words-dictionary'."))) + +(defun org-passwords-random-words-attach-number-of-words + (previous-string number-of-words list-of-words separator) + "Returns a string consisting of PREVIOUS-STRING followed by a +succession of NUMBER-OF-WORDS random words from the list LIST-OF-WORDS +separated SEPARATOR." + (if (eq number-of-words 0) + previous-string + (org-passwords-random-words-attach-number-of-words + (concat previous-string + separator + (nth (random (length list-of-words)) list-of-words)) + (1- number-of-words) + list-of-words + separator))) + +(defun org-passwords-substitute (string-to-change list-of-substitutions) + "Substitutes each appearence in STRING-TO-CHANGE of the `car' of +each element of LIST-OF-SUBSTITUTIONS by the `cdr' of that +element. For example: + (org-passwords-substitute \"ab\" \'((\"a\" . \"b\") (\"b\" . \"c\"))) + => \"bc\" +Substitutions are made in order of the list, so for example: + (org-passwords-substitute \"ab\" \'((\"ab\" . \"c\") (\"b\" . \"d\"))) + => \"c\"" + (if list-of-substitutions + (concat (org-passwords-concat-this-with-string + (cdar list-of-substitutions) + (mapcar (lambda (x) + (org-passwords-substitute + x + (cdr list-of-substitutions))) + (split-string string-to-change + (caar list-of-substitutions))))) + string-to-change)) + +(defun org-passwords-concat-this-with-string (this list-of-strings) + "Put the string THIS in between every string in LIST-OF-STRINGS. For example: + (org-passwords-concat-this-with-string \"Here\" \'(\"First\" \"Second\" \"Third\")) + => \"FirstHereSencondHereThird\"" + (if (cdr list-of-strings) + (concat (car list-of-strings) + this + (org-passwords-concat-this-with-string + (cdr list-of-strings) + this)) + (car list-of-strings))) + +(provide 'org-passwords) + +;;; org-passwords.el ends here