neopassmenu.sh 6.2 KB

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