-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathpw
executable file
·226 lines (183 loc) · 7.53 KB
/
pw
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
#!/bin/bash
passwordFilePath="${HOME}/.passwords" # No trailing slash
clipboardTime=30
configFile="${HOME}/.config/pw/private-key"
syncFile="${HOME}/.config/pw/sync-credentials"
recipient=""
usage(){
fmt <<EOF
DESCRIPTION
Create, edit, and view gpg-encrypted passwords stored in the ${passwordFilePath}/ folder.
By default, pw encrypts passwords symmetrically using a passphrase. To encrypt with a GPG key instead, create the file ~/.config/pw/private-key containing the email address of the private key to use. The private key must be on your keychain.
USAGE
pw [-l,--list]
List stored domains.
pw -g,--generate [DOMAIN]
Generate and print a new strong password to stdout and copy it to the clipboard for ${clipboardTime} seconds. If invoked with a domain name, additionally prompt for a login, then store the login and generated password in the password store.
pw -a,--add DOMAIN
Prompt for a login and password for a domain, then store those credentials in the password store.
pw -s,--show DOMAIN
Show stored credentials for the specified domain and copy the password to the clipboard for ${clipboardTime} seconds.
pw -e,--edit DOMAIN
Edit the information stored under that domain using a temporary file stored in a RAMFS mount. Requires sudo privileges.
pw -c,--sync [USER@HOST:DESTINATION]
Sync the ${passwordFilePath}/ folder to the USER@HOST:DESTINATION specified in ~/.config/pw/sync-credentials or as an argument.
EOF
exit
}
die(){ printf "Error: %s\n" "${1}" 1>&2; exit 1; }
require(){ command -v "${1}" > /dev/null 2>&1 || { suggestion=""; if [ ! -z "$2" ]; then suggestion=" $2"; fi; die "$1 is not installed.${suggestion}"; } }
if [ $# -eq 1 ]; then if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then usage; fi fi
# End boilerplate
specialCharacters="01234567890!@# $%^&*(){}.,:;"
tempMountPoint="/tmp/pw" # No trailing slash
require "gpg" "Try: apt-get install gnupg"
require "xclip" "Try: apt-get install xclip"
require "tree" "Try: apt-get install tree"
require "rsync" "Try: apt-get install rsync"
generatePassword(){
password=$(sort -R /usr/share/dict/words | head -n 100 | grep -i '^[a-z]*$' | head -n 4 | sed ':a;N;$!ba;s/\n/ /g')
randomPos=$[ ( ${RANDOM} % ${# password} ) + 1 ]
randomChar=${specialCharacters:$[ ( ${RANDOM} % ${# specialCharacters} ) + 1 ]-1:1}
password=$(echo "${password}" | sed "s/\(.\)/\1${randomChar}/${randomPos}")
randomPos=$[ ( ${RANDOM} % ${# password} ) + 1 ]
randomChar=${specialCharacters:$[ ( ${RANDOM} % ${# specialCharacters} ) + 1 ]-1:1}
password=$(echo "${password}" | sed "s/\(.\)/\1${randomChar}/${randomPos}")
randomPos=$[ ( ${RANDOM} % ${# password} ) + 1 ]
randomChar=${specialCharacters:$[ ( ${RANDOM} % ${# specialCharacters} ) + 1 ]-1:1}
password=$(echo "${password}" | sed "s/\(.\)/\1${randomChar}/${randomPos}")
randomPos=$[ ( ${RANDOM} % ${# password} ) + 1 ]
randomChar=${specialCharacters:$[ ( ${RANDOM} % ${# specialCharacters} ) + 1 ]-1:1}
password=$(echo "${password}" | sed "s/\(.\)/\1${randomChar}/${randomPos}")
printf "%s" "${password}"
}
syncPasswords(){
# Remove any trailing slashes, then add one
target=${1%/}
target="${target}/"
rsync -acz --progress --itemize-changes --partial -e ssh "${passwordFilePath}/" "${target}"
}
if [ -f "${configFile}" ]; then
recipient=$(cat "${configFile}")
fi
if [ $# -eq 0 ]; then
tree "${passwordFilePath}/" | sed "s/\.gpg$//" | sed -n -e :a -e '1,2!{P;N;D;};N;ba'
fi
if [ $# -eq 1 ]; then
if [ "$1" = "-g" ] || [ "$1" = "--generate" ]; then
password="$(generatePassword)"
echo "${password}"
printf "%s" "${password}" | xclip -selection clipboard
sleep "${clipboardTime}" && printf "" | xclip -selection clipboard &
elif [ "$1" = "-l" ] || [ "$1" = "--list" ]; then
tree "${passwordFilePath}" | sed "s/\.gpg$//" | sed -n -e :a -e '1,2!{P;N;D;};N;ba'
elif [ "$1" = "-c" ] || [ "$1" = "--sync" ]; then
if [ -f "${syncFile}" ]; then
destination=$(cat "${syncFile}")
syncPasswords "${destination}"
else
die "${syncFile} does not exist."
fi
fi
fi
if [ $# -eq 2 ]; then
if [ "$1" = "-g" ] || [ "$1" = "--generate" ]; then
if [ -f "${passwordFilePath}/$2.gpg" ]; then
printf "This domain already exists. Overwrite? (y/n) "
read yn
case "${yn}" in
[Yy]* ) :;;
[Nn]* ) exit;;
* ) exit;;
esac
fi
printf "Enter your login for this domain: "
read login
password="$(generatePassword)"
if [ "${recipient}" = "" ]; then
printf "login: ${login}\npass: ${password}" | gpg --symmetric --armor --batch --yes --output "${passwordFilePath}/$2.gpg"
else
printf "login: ${login}\npass: ${password}" | gpg --encrypt --armor --batch --yes --recipient "${recipient}" --output "${passwordFilePath}/$2.gpg"
fi
printf "%s" "${password}"
printf "%s" "${password}" | xclip -selection clipboard
sleep "${clipboardTime}" && printf "" | xclip -selection clipboard &
elif [ "$1" = "-a" ] || [ "$1" = "--add" ]; then
if [ -f "${passwordFilePath}/$2.gpg" ]; then
printf "This domain already exists. Overwrite? (y/n) "
read yn
case "${yn}" in
[Yy]* ) :;;
[Nn]* ) exit;;
* ) exit;;
esac
fi
printf "Enter your login for this domain: "
read login
printf "Enter your password for this domain: "
stty -echo
read password
stty echo
printf "\nRepeat your password for this domain: "
stty -echo
read passwordConfirm
stty echo
printf "\n"
if [ "${password}" != "${passwordConfirm}" ]; then
die "Passwords don't match."
fi
if [ "${recipient}" = "" ]; then
printf "login: ${login}\npass: ${password}" | gpg --symmetric --armor --batch --yes --output "${passwordFilePath}/$2.gpg"
else
printf "login: ${login}\npass: ${password}" | gpg --encrypt --armor --batch --yes --recipient "${recipient}" --output "${passwordFilePath}/$2.gpg"
fi
elif [ "$1" = "-s" ] || [ "$1" = "--show" ]; then
if [ -f "${passwordFilePath}/$2.gpg" ]; then
text=$(gpg --use-agent --decrypt --no-tty "${passwordFilePath}/$2.gpg" 2> /dev/null)
if [ $? -ne 0 ]; then
die "Invalid password."
fi
printf "%s\n" "${text}"
if printf "%s" "${text}" | grep -i -E "^pass(word)?\:\s.*" > /dev/null 2>&1; then
password=$(printf "%s" "${text}" | grep -i -E "^pass(word)?\:\s.*" | sed -r "s/^pass(word)?\:\s//")
if [ "${password}" != "" ]; then
printf "%s" "${password}" | xclip -selection clipboard
sleep "${clipboardTime}" && printf "" | xclip -selection clipboard &
fi
fi
else
die "File not found."
fi
elif [ "$1" = "-e" ] || [ "$1" = "--edit" ]; then
if [ -f "${passwordFilePath}/$2.gpg" ]; then
mkdir -p "${tempMountPoint}"
sudo umount "${tempMountPoint}" > /dev/null 2>&1
if ! sudo mount -t ramfs -o size=20m ramfs "${tempMountPoint}"; then
die "Couldn't create temporary mount. Do you have sudo privileges?"
fi
sudo chown "$(whoami)" "${tempMountPoint}"
filename="$(basename "$2")"
filePath="${tempMountPoint}/${filename}"
if ! gpg --use-agent --decrypt --no-tty --output "${filePath}" "${passwordFilePath}/$2.gpg" 2> /dev/null; then
sudo umount "${tempMountPoint}"
die "Invalid password."
fi
if [ "${EDITOR}" = "" ]; then
vi "${filePath}"
else
"${EDITOR}" "${filePath}"
fi
if [ "${recipient}" = "" ]; then
gpg --symmetric --armor --batch --yes --output "${passwordFilePath}/$2.gpg" "${filePath}"
else
gpg --encrypt --armor --batch --yes --recipient "${recipient}" --output "${passwordFilePath}/$2.gpg" "${filePath}"
fi
rm "${filePath}"
sudo umount "${tempMountPoint}"
else
die "File not found."
fi
elif [ "$1" = "-c" ] || [ "$1" = "--sync" ]; then
syncPasswords "$2"
fi
fi