1 ;;; org-passwords.el --- org derived mode for managing passwords
3 ;; Author: Jorge A. Alfaro-Murillo <jorge.alfaro-murillo@yale.edu>
4 ;; Created: December 26, 2012
5 ;; Keywords: passwords, password
7 ;; This file is NOT part of GNU Emacs.
9 ;; This program is free software: you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation, either version 3 of the License, or
12 ;; (at your option) any later version.
14 ;; This program is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;; GNU General Public License for more details.
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
21 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
25 ;; This file contains the code for managing your passwords with
26 ;; Org-mode. It is part of org/contrib (see http://orgmode.org/). If
27 ;; you want to contribute with development, or have a problem, do it
28 ;; here: https://bitbucket.org/alfaromurillo/org-passwords.el
30 ;; A basic setup needs to indicate a passwords file, and a dictionary
31 ;; for the random words:
33 ;; (require 'org-passwords)
34 ;; (setq org-passwords-file "~/documents/passwords.gpg")
35 ;; (setq org-passwords-random-words-dictionary "/etc/dictionaries-common/words")
39 ;; `M-x org-passwords' opens the passwords file in
40 ;; `org-passwords-mode'.
42 ;; `M-x org-passwords-generate-password' generates a random string
43 ;; of numbers, lowercase letters and uppercase letters.
45 ;; `C-u M-x org-passwords-generate-password' generates a random
46 ;; string of numbers, lowercase letters, uppercase letters and
49 ;; `M-x org-passwords-random-words' concatenates random words from
50 ;; the dictionary defined by `org-passwords-random-words-dictionary'
51 ;; into a string, each word separated by the string defined in
52 ;; `org-passwords-random-words-separator'.
54 ;; `C-u M-x org-passwords-random-words' does the same as above, and
55 ;; also makes substitutions according to
56 ;; `org-passwords-random-words-substitutions'.
58 ;; It is also useful to set up keybindings for the functions
59 ;; `org-passwords-copy-username', `org-passwords-copy-password' and
60 ;; `org-passwords-open-url' in the `org-passwords-mode', to easily
61 ;; make the passwords and usernames available to the facility for
62 ;; pasting text of the window system (clipboard on X and MS-Windows,
63 ;; pasteboard on Nextstep/Mac OS, etc.), without inserting them in the
64 ;; kill-ring. You can set for example:
66 ;; (eval-after-load "org-passwords"
68 ;; (define-key org-passwords-mode-map
70 ;; 'org-passwords-copy-username)
71 ;; (define-key org-passwords-mode-map
73 ;; 'org-passwords-copy-password)
75 ;; 'org-passwords-open-url)))
77 ;; Finally, to enter new passwords, you can use `org-capture' and a
78 ;; minimal template like:
80 ;; ("p" "password" entry (file "~/documents/passwords.gpg")
81 ;; "* %^{Title}\n %^{URL}p %^{USERNAME}p %^{PASSWORD}p")
83 ;; When asked for the password you can then call either
84 ;; `org-passwords-generate-password' or `org-passwords-random-words'.
85 ;; Be sure to enable recursive minibuffers to call those functions
86 ;; from the minibuffer:
88 ;; (setq enable-recursive-minibuffers t)
95 (define-derived-mode org-passwords-mode org-mode
97 "Mode for storing passwords"
100 (defgroup org-passwords nil
101 "Options for password management."
104 (defcustom org-passwords-password-property
"PASSWORD"
105 "Name of the property for password entry."
107 :group
'org-passwords
)
109 (defcustom org-passwords-username-property
"USERNAME"
110 "Name of the property for user name entry."
112 :group
'org-passwords
)
114 (defcustom org-passwords-url-property
"URL"
115 "Name of the property for URL entry."
117 :group
'org-passwords
)
119 (defcustom org-passwords-file nil
120 "Default file name for the file that contains the passwords."
122 :group
'org-passwords
)
124 (defcustom org-passwords-time-opened
"1 min"
125 "Time that the password file will remain open. It has to be a
126 string, a number followed by units."
128 :group
'org-passwords
)
130 (defcustom org-passwords-default-password-size
"20"
131 "Default number of characters to use in
132 org-passwords-generate-password. It has to be a string."
134 :group
'org-passwords
)
136 (defcustom org-passwords-random-words-dictionary nil
137 "Default file name for the file that contains a dictionary of
138 words for `org-passwords-random-words'. Each non-empty line in
139 the file is considered a word."
141 :group
'org-passwords
)
143 (defcustom org-passwords-default-random-words-number
"5"
144 "Default number of words to use in org-passwords-random-words.
145 It has to be a string."
147 :group
'org-passwords
)
149 (defvar org-passwords-random-words-separator
"-"
150 "A string to separate words in `org-passwords-random-words'.")
152 (defvar org-passwords-random-words-substitutions
156 "A list of substitutions to be made with
157 `org-passwords-random-words' if it is called with
158 `universal-argument'. Each element is pair of
159 strings (SUBSTITUTE-THIS . BY-THIS).")
161 (defun org-passwords-copy-password ()
162 "Makes the password available to other programs. Puts the
163 password of the entry at the location of the cursor in the
164 facility for pasting text of the window system (clipboard on X
165 and MS-Windows, pasteboard on Nextstep/Mac OS, etc.), without
166 putting it in the kill ring."
168 (funcall interprogram-cut-function
169 (org-entry-get (point)
170 org-passwords-password-property
)))
172 (defun org-passwords-copy-username ()
173 "Makes the password available to other programs. Puts the
174 username of the entry at the location of the cursor in the
175 facility for pasting text of the window system (clipboard on X
176 and MS-Windows, pasteboard on Nextstep/Mac OS, etc.), without
177 putting it in the kill ring."
179 (funcall interprogram-cut-function
180 (org-entry-get (point)
181 org-passwords-username-property
184 (defun org-passwords-open-url ()
185 "Browse the URL associated with the entry at the location of
188 (browse-url (org-entry-get (point)
189 org-passwords-url-property
193 (defun org-passwords (&optional arg
)
194 "Open the password file. Open the password file defined by the
195 variable `org-password-file' in read-only mode and kill that
196 buffer later according to the value of the variable
197 `org-passwords-time-opened'. It also adds the `org-password-file'
198 to the auto-mode-alist so that it is opened with its mode being
199 `org-passwords-mode'.
201 With prefix arg ARG, the command does not set up a timer to kill the buffer.
203 With a double prefix arg \\[universal-argument] \\[universal-argument], open the file for editing.
206 (if org-passwords-file
208 (add-to-list 'auto-mode-alist
211 (expand-file-name org-passwords-file
))
212 'org-passwords-mode
))
214 (find-file-read-only org-passwords-file
)
215 (if (equal arg
'(16))
216 (find-file org-passwords-file
)
218 (find-file-read-only org-passwords-file
)
219 (org-passwords-set-up-kill-password-buffer)))))
220 (minibuffer-message "No default password file defined. Set the variable `org-password-file'.")))
222 (defun org-passwords-set-up-kill-password-buffer ()
223 (run-at-time org-passwords-time-opened
226 (if (get-file-buffer org-passwords-file
)
228 (get-file-buffer org-passwords-file
))))))
230 ;;; Password generator
232 ;; Set random number seed from current time and pid. Otherwise
233 ;; `random' gives the same results every time emacs restarts.
236 (defun org-passwords-generate-password (arg)
237 "Ask a number of characters and insert a password of that size.
238 Password has a random string of numbers, lowercase letters, and
239 uppercase letters. Argument ARG include symbols."
241 (let ((number-of-chars
242 (read-from-minibuffer
243 (concat "Number of characters (default "
244 org-passwords-default-password-size
250 org-passwords-default-password-size
)))
252 (insert (org-passwords-generate-password-with-symbols "" number-of-chars
))
253 (insert (org-passwords-generate-password-without-symbols "" number-of-chars
)))))
255 (defun org-passwords-generate-password-with-symbols (previous-string nums-of-chars
)
256 "Return a string consisting of PREVIOUS-STRING and
257 NUMS-OF-CHARS random characters."
258 (if (eq nums-of-chars
0) previous-string
259 (org-passwords-generate-password-with-symbols
260 (concat previous-string
262 ;; symbols, letters, numbers are from 33 to 126
263 (+ (random (- 127 33)) 33)))
264 (1- nums-of-chars
))))
266 (defun org-passwords-generate-password-without-symbols (previous-string nums-of-chars
)
267 "Return string consisting of PREVIOUS-STRING and NUMS-OF-CHARS
268 random numbers, lowercase letters, and numbers."
269 (if (eq nums-of-chars
0)
271 ; There are 10 numbers, 26 lowercase letters and 26 uppercase
272 ; letters. 10 + 26 + 26 = 62. The number characters go from 48
273 ; to 57, the uppercase letters from 65 to 90, and the lowercase
274 ; from 97 to 122. The following makes each equally likely.
275 (let ((temp-value (random 62)))
276 (cond ((< temp-value
10)
277 ; If temp-value<10, then add a number
278 (org-passwords-generate-password-without-symbols
279 (concat previous-string
280 (char-to-string (+ 48 temp-value
)))
282 ((and (> temp-value
9) (< temp-value
36))
283 ; If 9<temp-value<36, then add an uppercase letter
284 (org-passwords-generate-password-without-symbols
285 (concat previous-string
286 (char-to-string (+ 65 (- temp-value
10))))
289 ; If temp-value>35, then add a lowecase letter
290 (org-passwords-generate-password-without-symbols
291 (concat previous-string
292 (char-to-string (+ 97 (- temp-value
36))))
293 (1- nums-of-chars
)))))))
297 (defun org-passwords-random-words (arg)
298 "Ask for a number of words and inserts a sequence of that many
299 random words from the list in the file
300 `org-passwords-random-words-dictionary' separated by
301 `org-passwords-random-words-separator'. ARG make substitutions in
302 the words as defined by
303 `org-passwords-random-words-substitutions'."
305 (if org-passwords-random-words-dictionary
306 (let ((number-of-words
307 (read-from-minibuffer
308 (concat "Number of words (default "
309 org-passwords-default-random-words-number
315 org-passwords-default-random-words-number
))
318 (insert-file-contents
319 org-passwords-random-words-dictionary
)
320 (split-string (buffer-string) "\n" t
))))
322 (org-passwords-substitute
323 (org-passwords-random-words-attach-number-of-words
324 (nth (random (length list-of-words
))
328 org-passwords-random-words-separator
)
330 org-passwords-random-words-substitutions
333 "No default dictionary file defined. Set the variable `org-passwords-random-words-dictionary'.")))
335 (defun org-passwords-random-words-attach-number-of-words
336 (previous-string number-of-words list-of-words separator
)
337 "Returns a string consisting of PREVIOUS-STRING followed by a
338 succession of NUMBER-OF-WORDS random words from the list LIST-OF-WORDS
339 separated SEPARATOR."
340 (if (eq number-of-words
0)
342 (org-passwords-random-words-attach-number-of-words
343 (concat previous-string
345 (nth (random (length list-of-words
)) list-of-words
))
350 (defun org-passwords-substitute (string-to-change list-of-substitutions
)
351 "Substitutes each appearence in STRING-TO-CHANGE of the `car' of
352 each element of LIST-OF-SUBSTITUTIONS by the `cdr' of that
353 element. For example:
354 (org-passwords-substitute \"ab\" \'((\"a\" . \"b\") (\"b\" . \"c\")))
356 Substitutions are made in order of the list, so for example:
357 (org-passwords-substitute \"ab\" \'((\"ab\" . \"c\") (\"b\" . \"d\")))
359 (if list-of-substitutions
360 (concat (org-passwords-concat-this-with-string
361 (cdar list-of-substitutions
)
363 (org-passwords-substitute
365 (cdr list-of-substitutions
)))
366 (split-string string-to-change
367 (caar list-of-substitutions
)))))
370 (defun org-passwords-concat-this-with-string (this list-of-strings
)
371 "Put the string THIS in between every string in LIST-OF-STRINGS. For example:
372 (org-passwords-concat-this-with-string \"Here\" \'(\"First\" \"Second\" \"Third\"))
373 => \"FirstHereSencondHereThird\""
374 (if (cdr list-of-strings
)
375 (concat (car list-of-strings
)
377 (org-passwords-concat-this-with-string
379 (cdr list-of-strings
)))
380 (car list-of-strings
)))
382 (provide 'org-passwords
)
384 ;;; org-passwords.el ends here