neopassmenu.sh 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. #!/usr/bin/env sh
  2. multi=false
  3. clipboard=false
  4. type=false
  5. add=false
  6. otp=false
  7. remove=false
  8. edit=false
  9. file=""
  10. dir=""
  11. coffin=false
  12. checkmenu(){
  13. for i in "$@"; do
  14. if command -v "$i" >/dev/null 2>&1; then
  15. echo "$i";
  16. break;
  17. fi
  18. done
  19. }
  20. fileopener(){
  21. editor="$(xdg-mime query default text/plain)"
  22. [ -f "$HOME/.local/share/applications/${editor}" ] && path="$HOME/.local/share/applications/${editor}"
  23. [ -z "$path" ] && [ -f "/usr/local/share/applications/${editor}" ] && path="/usr/local/share/applications/${editor}"
  24. [ -z "$path" ] && [ -f "/usr/share/applications/${editor}" ] && path="/usr/share/applications/${editor}"
  25. if [ -z "$path" ]; then
  26. echo "Text editor cannot be found" >&2
  27. fi
  28. if grep -q "^Terminal=true$" "$path"; then
  29. eval "${TERMINAL_EXEC:-xterm -e} $(grep "^Exec=" "$path" | head -n 1 | sed "{s/^Exec=//; s#%.#$1#}")"
  30. else
  31. eval "$(grep "^Exec=" "$path" | head -n 1 | sed "{s/^Exec=//; s#%.#$1#}")"
  32. fi
  33. }
  34. testcoffin(){
  35. if pass open 1>&2; then
  36. coffin=true
  37. fi
  38. }
  39. _exit(){
  40. $coffin && pass close 1>&2
  41. exit "$1"
  42. }
  43. if [ -n "$WAYLAND_DISPLAY" ]; then
  44. x11=false
  45. if [ -z "$DMENU_COMMAND" ]; then
  46. dmenu="$(checkmenu dmenu-wl bemenu tofi yofi wofi)"
  47. case "$dmenu" in
  48. yofi)
  49. dmenu="yofi dialog"
  50. ;;
  51. wofi)
  52. dmenu="wofi -dmenu"
  53. ;;
  54. esac
  55. else
  56. dmenu="$DMENU_COMMAND"
  57. fi
  58. else
  59. x11=true
  60. if [ -z "$DMENU_COMMAND" ]; then
  61. dmenu="$(checkmenu dmenu rofi bemenu)"
  62. case "$dmenu" in
  63. rofi)
  64. dmenu="rofi -dmenu"
  65. ;;
  66. esac
  67. else
  68. dmenu="$DMENU_COMMAND"
  69. fi
  70. fi
  71. prompt_prefix="$(case "$(echo "$dmenu" | cut -d " " -f 1)" in
  72. tofi)
  73. printf "%s" "--prompt="
  74. ;;
  75. *)
  76. printf "%s" "-p "
  77. ;;
  78. esac
  79. )"
  80. if [ -z "$dmenu" ]; then
  81. echo "No menu found, specify menu with the DMENU_COMMAND environment variable or install a known menu"
  82. _exit 1;
  83. fi
  84. usage="$(printf "USAGE:\nneopassmenu [OPTS] [Pass Entry]\n\nOptions:\n-a\t\tGenerate a password\n-e\t\tEdit a password\n-c\t\tCopy to clipboard instead of printing to stdout\n-t\t\tSimulate typing instead of printing to stdout\n-m\t\tSelect a specific line from a multiline file\n-o\t\tGet an otp instead of the password file (precedence over -m)\n-r\t\tRemove password\n-h\t\tPrint usage information\n\nCONFIGURATION:\nEnvironement variables:\nDMENU_COMMAND\tSpecify dmenu command (default: dmenu)\nTERMINAL\tSpecify the terminal command (default: xterm -e)")"
  85. # Open eventual coffin before reading
  86. cd "${PASSWORD_STORE_DIR:-"$HOME/.password-store"}" || _exit 2
  87. while [ "$#" -gt 0 ]; do
  88. case "$1" in
  89. -a)
  90. add=true
  91. shift
  92. ;;
  93. -c)
  94. clipboard=true
  95. type=false
  96. shift
  97. ;;
  98. -t)
  99. clipboard=false
  100. type=true
  101. shift
  102. ;;
  103. -m)
  104. multi=true
  105. shift
  106. ;;
  107. -h)
  108. echo "$usage"
  109. _exit 0
  110. ;;
  111. -o)
  112. otp=true
  113. shift
  114. ;;
  115. -r)
  116. remove=true
  117. shift
  118. ;;
  119. -e)
  120. edit=true
  121. shift
  122. ;;
  123. *)
  124. if [ -f "$1" ] || [ -f "${1}.gpg" ]; then
  125. file="$1"
  126. shift
  127. elif [ -d "$1" ]; then
  128. dir="$1"
  129. shift
  130. else
  131. echo "$usage" >&2
  132. _exit 1
  133. fi
  134. ;;
  135. esac
  136. done
  137. if ($add && $remove) || ($add && $edit) || ($edit && remove); then
  138. msg="Incompatible operations: $(printf "%s%s%s" "$($add && echo "add ")" "$($remove && echo "remove ")" "$($edit && echo "edit")")"
  139. echo "$msg" 1>&2
  140. notify-send -a "neopassmenu" -h "string:sound-name:dialog-error" "Error" "$msg"
  141. _exit 1
  142. fi
  143. testcoffin
  144. if [ -z "$file" ]; then
  145. while true; do
  146. file="$(printf "..\n%s" "$(find "./$dir" -maxdepth 1)" | sed -e 's#^'"./$dir"'##;s#^/##;s/.gpg$//' -e '/^$/d; /^.gpg-id$/d; /^.git\(attributes\)\{0,1\}$/d' | $dmenu $prompt_prefix"Choose Password")"
  147. if [ -z "$dir" ]; then
  148. newdir="$file"
  149. else
  150. newdir="${dir%/}/$file"
  151. fi
  152. if [ -z "$file" ]; then
  153. _exit 127
  154. elif [ "$file" = ".." ]; then
  155. dir="${dir%/}"
  156. if echo "$dir" | grep -q "/"; then
  157. dir="${dir%/*}"
  158. else
  159. dir=""
  160. fi
  161. elif [ -d "$newdir" ]; then
  162. dir="$newdir"
  163. elif [ -f "$newdir" ] || [ -f "${newdir}.gpg" ] || "$add"; then
  164. file="$newdir"
  165. break
  166. else
  167. break
  168. fi
  169. done
  170. fi
  171. if "$add" || "$edit"; then
  172. if $otp; then
  173. command="append"
  174. if ! [ -f "${file}.gpg" ]; then
  175. command="add"
  176. else
  177. if pass otp "${file}"; then
  178. notify-send -a "neopassmenu" -h "string:sound-name:message" "OTP" "Password ${file} already has OTP set up"
  179. _exit 0
  180. fi
  181. otp="$(echo "" | $dmenu -p "OTP URI ($command): ")"
  182. [ -n "$otp" ] && echo "$otp" | pass otp "$command" "$file"
  183. fi
  184. _exit 0
  185. fi
  186. if [ -f "${file}.gpg" ]; then
  187. pass "$file" > ~/.cache/pass.txt
  188. fi
  189. $add && (printf "\n\n%s" "$(dd count=2 if=/dev/random of=/dev/stdout 2>/dev/null ibs=512 obs=512 | uuencode -m /dev/stdout | tail -n +2 | tr -d '\n')" >> ~/.cache/pass.txt)
  190. fileopener ~/.cache/pass.txt || notify-send "Error $?"
  191. if [ -n "$(cat ~/.cache/pass.txt)" ]; then
  192. save="$(printf "yes\nno" | $dmenu $prompt_prefix"Save changes? ")"
  193. if [ "$save" = "yes" ]; then
  194. pass add -m -f "$file" < ~/.cache/pass.txt
  195. fi
  196. rm ~/.cache/pass.txt
  197. _exit 0
  198. fi
  199. rm ~/.cache/pass.txt
  200. _exit 1
  201. fi
  202. if ! [ -f "$file" ] && ! [ -f "${file}.gpg" ]; then
  203. _exit 127
  204. fi
  205. if $remove; then
  206. pass rm file
  207. _exit $?
  208. fi
  209. if ! $otp; then
  210. password_lines="$(pass "$file")"
  211. else
  212. password_lines="$(pass otp "$file")"
  213. fi
  214. if $multi && ! $otp; then
  215. sel_password="$(echo "$password_lines" | sed '{h; s/^.\{1,2\}//; s/./*/g; x; s/^\(.\{1,2\}\).*/\1/; G; s/\n//}' | nl -b a -w 1 -s " " | $dmenu | cut -d " " -f 1)"
  216. if echo "$sel_password" | grep -qE "^[0-9]+$"; then
  217. password_lines="$(echo "$password_lines" | sed -n "$(echo "$sel_password" | cut -d " " -f 1){p;q}")"
  218. else
  219. password_lines=""
  220. fi
  221. # Sed script:
  222. # Copy pattern space (line) to hold space. Pattern space: Foo Hold space: Foo
  223. # Delete first or first two characters in pattern space. Pattern space: o Hold space: Foo
  224. # Replace every character in patter space with "*", Pattern space: * Hold Space: Foo
  225. # Swap pattern space with hold space. Pattern Space: Foo Hold space: *
  226. # Delete all the characters in pattern space after the first two. Pattern Space: Fo Hold Space: *
  227. # Append newline followed by the Hold Space to the Pattern Space. Pattern Space: Fo\n* Hold Space: *
  228. # Remove the (supposedly only) newline from the pattern space. Pattern Space: Fo* Hold Space: *
  229. # Sed automatically prints the pattern space at the end of a script (unless the -n option is given). Output: Fo*
  230. fi
  231. if [ -z "$password_lines" ]; then
  232. echo "No password found or selected" >&2
  233. _exit 1
  234. fi
  235. # Finished reading passwords, it's probably safe to close the coffin
  236. if $clipboard; then
  237. if $x11; then
  238. printf "%s" "$password_lines" | xclip -in -selection clipboard
  239. else
  240. printf "%s" "$password_lines" | wl-copy -n
  241. fi
  242. _exit 0
  243. elif $type; then
  244. if $x11; then
  245. printf "%s" "$password_lines" | xdotool type --clearmodifiers --file -
  246. else
  247. printf "%s" "$password_lines" | ydotool type --file -
  248. fi
  249. _exit 0
  250. fi
  251. echo "$password_lines"
  252. $coffin && _exit 0