Compare commits

..
124 Commits
Author SHA1 Message Date
RandyTheSilly 921630f5a8 Re-organized badges
Former-commit-id: b6055b13fc6cdb1b629ea4a76e3f5997fbbbf33f [formerly fdc014c44a]
Former-commit-id: 3c86294a4149f4395a2314e58a8c0d1aef4a43fb
2022-12-14 02:52:23 -05:00
RandyTheSilly 627c6a1a14 Update documentation for release v1.3.0
Former-commit-id: ac89c761d964360d80863c926f908d937ed00865 [formerly 697f7a870d]
Former-commit-id: fde0008b65ef6135476a448dd1cc768a7e511cb8
2022-12-14 02:40:13 -05:00
RandyTheSilly a9f4435493 Replaced use of system-provided chmod and find with os.chmod and os.walk, respectively
Former-commit-id: 89be833d3e15aaf00578a093ce5b5efb8c3b84f7 [formerly bad92878e4]
Former-commit-id: 1871adcc80cd91666325fe0a3db3771c9256357c
2022-12-14 01:05:22 -05:00
RandyTheSilly 7d639ecca9 Use getpass() for quick-unlock password input
Former-commit-id: b7312096d7b58ddea540f08064ea536fb6d0543c [formerly b4fb37d877]
Former-commit-id: 672c2e53a6827119a85a41e435508453f693009e
2022-12-11 18:21:31 -05:00
RandyTheSilly eefe5e39b5 Fixed deletions occuring after the local index is generated, causing sshync to believe sheared files still exist on the client
Former-commit-id: 0f303028224689cb7b5e290d28ad30d7ea0a0776 [formerly 4ebbf7847d]
Former-commit-id: 348fa6dcc8b6f35c102c9cd9c3912d161bc200e7
2022-12-07 22:29:15 -05:00
RandyTheSilly c28b836c2a Updated packaging script to accommodate the removal of sshypRemote.py
Former-commit-id: ecdc146022c96c463d4ce0bbb3601eb2f128b582 [formerly af44f244db]
Former-commit-id: 42e50a6d827987551163a386b78c487502defe8d
2022-12-07 20:43:56 -05:00
RandyTheSilly b53370a3e2 Strip entry/folder names after user input to ensure compatibility with sshync
Former-commit-id: f07a48fc2bde0d651199d62cb0f35a1c18f51f8e [formerly 9d8eca3a2b]
Former-commit-id: 438ac436902369f1af4dc8dae5abc38d7e6dabaf
2022-12-07 20:27:39 -05:00
RandyTheSilly 9776961f2a Assigned exit code 6 to "server connection error"
Former-commit-id: 34c2312b779b7c0f6e6dfcd1e9b4d7e700229142 [formerly b24d1df721]
Former-commit-id: 72c0e4cef04abd78bffb3731434c55cf9998eb7b
2022-12-07 15:55:01 -05:00
RandyTheSilly 1a35aaf33b Combined all list fetching functions into one to drastically reduce the number of SSH connections (speeds up sshync exponentially)
Former-commit-id: 11290b3915a2f3d92696e0953997c3fb293ec7e3 [formerly 6080726dc6]
Former-commit-id: 3f799d1c8e2d3cd694cad90ba9f4444bb2673934
2022-12-07 15:46:33 -05:00
RandyTheSilly 66b15ac22c Organized functions in sshync.py by use method
Former-commit-id: 1bbcac88ffe5c4d08aac1f34c1a40ce0fbff092e [formerly a49334482c]
Former-commit-id: ba2ee6fa69228ef2fe52560f4aa22c39ec69c9ab
2022-12-05 23:21:02 -05:00
RandyTheSilly 56db2e65f8 Moved all syncing functionality into sshync.py, removed sshypRemote.py
Former-commit-id: bf7833a21db35a7d33fdd89ee8a41775fd2f55c0 [formerly 56ea8d0cf9]
Former-commit-id: 820f85f9b0efc8a33ebf8a33f751e8f130fc8c34
2022-12-05 12:13:28 -05:00
RandyTheSilly 1be67d404b Call specifically 'python3' over ssh, as some environments do not have a binary/symlink named 'python', or 'python' may refer to python2
Former-commit-id: db4e264c648a05a9d0293873c5ffae6173e4f8a0 [formerly 6f016bfb0a]
Former-commit-id: f4635300ddda1927fa89b63edf0dc0be7729abbf
2022-12-05 09:36:37 -05:00
RandyTheSilly a279819260 Replaced use of system-provided 'ls' with 'os.listdir'
Former-commit-id: cbd6302ec15624381303d1181861d3c2792475a6 [formerly 31a4c7b8e7]
Former-commit-id: acd4d9db5d557793aca4662e48ca88320b7a3bfa
2022-12-04 18:57:23 -05:00
RandyTheSilly 578f7dee14 Replaced use of system-provided 'touch' with pathlib's Path.touch, added 'mode=' when specifying permissions
Former-commit-id: 7ca6d3e2ef3ff494932a849d9ea576218ec5e054 [formerly 45e2b60e10]
Former-commit-id: b477cbd7b3771aa8a513d4ef481e725c59b4bdf4
2022-12-04 18:33:41 -05:00
RandyTheSilly 5bec4fdaa9 Detect Wayland sessions by checking if $WAYLAND_DISPLAY is set
Former-commit-id: 584427e2b5f0d8a1cad1c1fa30b5024d976b9a14 [formerly d7987a1bc0]
Former-commit-id: c79d54b2d7fb8ff2f1283c388ad5aa1c16a7dcd1
2022-12-04 17:45:50 -05:00
RandyTheSilly 5f903966da Re-ordered clipboard support to match support priorities (Wayland first)
Former-commit-id: f14120402a6fde4a3d3124b7965e1ec5de70d8b4 [formerly 952fdd160b]
Former-commit-id: 1d96d2fbfda35b617ec7cfef1530bcba1d650601
2022-12-04 17:38:10 -05:00
RandyTheSilly cfd551ef7c Replace occasional use of system-provided mkdir with pathlib's Path.mkdir
Former-commit-id: 72c92e8f07ad9f3933c2593f426e2c05b194c3b3 [formerly c8c394e550]
Former-commit-id: d6eb5469dfd16a538da5fff5dcd6525aa3995234
2022-12-04 15:31:31 -05:00
RandyTheSilly 4d22b5b5a8 Removed all remaining instances of subprocess.run being called with shell=True
Former-commit-id: c6d663cf3af2e5a06f13e5cb3571d668880f6d6e [formerly db4351469e]
Former-commit-id: cba35421c41474e5daf5315f34b1f6d86380bc24
2022-11-28 14:17:26 -05:00
RandyTheSilly 46d5f8d515 Replace remaining uses of system()
Former-commit-id: 5a8ca97389b8d37c8050ba23d1e455357f5be0b1 [formerly e8703300fd]
Former-commit-id: 81b4d2783d5b09cc31515ab22f0f42e5ed55218b
2022-11-28 13:38:17 -05:00
RandyTheSilly 02ee2a5c0e Allow Haiku to use default pinentry, thanks to haikuports/haikuports#7457
Former-commit-id: 70edaa9d4c2a88d483d1247dc5682a30f0d593e5 [formerly 322db7ac54]
Former-commit-id: 6677ac5f2ae91f5b7a8c6656432ef962013592ff
2022-11-28 12:52:54 -05:00
RandyTheSilly fb1c3844a8 Use XDG_SESSION_TYPE to check for Wayland, rather than WAYLAND_DISPLAY
Former-commit-id: 4598a3038d49df81426dfcf2a29a366ea6bc4c74 [formerly cc81f219fa]
Former-commit-id: 4c8b1ddc2d9f7cee44007de0e2ae4cbea882a20e
2022-11-26 23:57:12 -05:00
RandyTheSilly 213ca41582 Renaming no longer throws an error while not in offline mode
Former-commit-id: e69e157a951cfc4151e3e511585c04a24b5a08ee [formerly bf20b83738]
Former-commit-id: dd90cb9f0518b9071b170a6b549cb71a352cd249
2022-11-26 15:18:47 -05:00
RandyTheSilly b8dc5c4f89 Move some rarely used import statements closer to where they are needed
Former-commit-id: a4785c178b5da044d1cf2ec2ecb2868ed8e3e877 [formerly 74f9f616f3]
Former-commit-id: c7c06f402627cc0169ed9e654086e89123639902
2022-11-26 14:05:19 -05:00
RandyTheSilly 574637392a Reduced code used for clipboard support
Former-commit-id: 2a7ac0d078526e60f742f8bdee1689f9f27df3f2 [formerly daf3f0d7bf]
Former-commit-id: d4d0683c63e19c3b9fdc33d02c105244e037035c
2022-11-26 13:53:47 -05:00
RandyTheSilly c6f31da6d4 Replace all currently replaceable instances of system() or run(X, shell=True) with run(X, shell=False) for security and compatibility
Former-commit-id: 8e316228ca46dbe9e6811625281c19a1118ff650 [formerly dc218025a7]
Former-commit-id: 7d19e68ccbd2b844ab00df8db69bbf08b9a1884e
2022-11-26 13:19:09 -05:00
RandyTheSilly 0f4c57bec3 Attempt to create ~/.ssh regardless of OS
Former-commit-id: 424cb44c13e9512d57cf6e04215cf975421e1336 [formerly 3a63966827]
Former-commit-id: b5847a53398576802f71ecddf005606de6d97481
2022-11-25 21:40:23 -05:00
RandyTheSilly 51b1b8c6ec Use os.symlink in place of shell commands
Former-commit-id: 3c62f4bb691a7c8d4b7419697c0e81e5826de736 [formerly 3ad2068b11]
Former-commit-id: 6fd9419e1904efabae07af45293da4f56c3b3188
2022-11-25 21:32:18 -05:00
RandyTheSilly 5f0a40efc0 Removed exit code 6 from man page, as it no longer has reason to exist in sshyp
Former-commit-id: 56f1761cbc55f7a348db132dfd7a8714bfe2fac7 [formerly 9b9f49a344]
Former-commit-id: 205e9dbbff27b8732024a8e602786afc104f050c
2022-11-25 21:17:41 -05:00
RandyTheSilly 05c034652f Replaced database downloads in sync() with newer output capture logic from sshync - much faster syncing
Former-commit-id: f6a9dce34263b3385440e6c166f9880f4781b5d5 [formerly 8276b9164e]
Former-commit-id: 850bf5889c2cf401ad9e91da5512a7455a353e6d
2022-11-25 21:11:07 -05:00
RandyTheSilly 1d185b1723 Removed useless pass statement in sync()
Former-commit-id: 12eebb98186597f164a3cc38306f67758273e506 [formerly dce6ad4390]
Former-commit-id: 4275468f89557716a8eec72183caaf2eccd5aad4
2022-11-25 19:37:13 -05:00
RandyTheSilly bca2af0ed8 pass_gen no longer recurses when the user gives invalid input
Former-commit-id: c9756093cd861911b7f5dc05a71fc5a8b858c17e [formerly b10c111640]
Former-commit-id: c8b7974ec39c4e114578314814c5c2a982339736
2022-11-25 19:23:14 -05:00
RandyTheSilly d301cf6298 Import os.path.expanduser directly to remove lots of repeated text
Former-commit-id: 247c04a8aa9720fd0a06a128d292a21cca2cf08c [formerly 3a96ef29dc]
Former-commit-id: 3f97f7d5199c477f311789a6c5c3f38c4624d10e
2022-11-25 18:45:06 -05:00
RandyTheSilly 89c9a03cc1 Use subprocess.run without shell=True where easily applicable, redirect unused captured output to DEVNULL
Former-commit-id: bae14d658f4e1fa04f723e8545bc35d69e0c4cbb [formerly 3a85f06ffe]
Former-commit-id: 7b7b48c870b9a6d4646d9be3d208ea792e665399
2022-11-25 18:11:38 -05:00
RandyTheSilly 3392e3ceea Use ASCII 30 and 31 as separators, removes limitations on "@", "^&*", and "*&^".
Former-commit-id: cd08e1adde9f810e0099338722b617357055c422 [formerly 289d12f45a]
Former-commit-id: 7f70d015e3afe2101aba2d1d21001101db0de81e
2022-11-25 12:00:51 -05:00
RandyTheSilly 176ee623cf Replaced sshync mod time/title retrieval logic with much more efficient and compatible logic
Former-commit-id: a9107238fd80398b570ca04ac36e212bcb3c0ddc [formerly ae5172a6c8]
Former-commit-id: 4984678e5544d0594bbcbe10b23100427ca2e8b8
2022-11-24 19:40:04 -05:00
RandyTheSilly 42abb7b674 Corrected Fedora packaging
Former-commit-id: 5fc5021f68b98bcb71df7794f2c7961445926c38 [formerly d1c968ed12]
Former-commit-id: 7386503874acef51bb1f02b02eab4637b94dff60
2022-11-06 14:00:51 -05:00
RandyTheSilly 3a6655e977 Removed '~' from generated filename character pool to avoid issues with quick-unlock
Former-commit-id: bcb77c5030c48693bcd0a479f7576e6ddc4d6729 [formerly 109141a266]
Former-commit-id: b7bd4134b3bee5760aaa56064a1ad9b29957fa1c
2022-11-06 13:25:35 -05:00
RandyTheSilly 9d1f0a065b Corrected date in man page
Former-commit-id: 61310403c6cfe504e1b8453c29e564459cd08bef [formerly 57cc730d92]
Former-commit-id: 3f614eb15a35f252188ee0a807c5b324ce409084
2022-11-06 11:21:21 -05:00
RandyTheSilly 8298966d8f Fixed documentation location in Haiku
Former-commit-id: 4d7a9a8f9b18efd21cb4273dc2dcaed189fdd21e [formerly 790ac573e5]
Former-commit-id: 2d7cb8bb642a96080be8c6396506cd7257d76bca
2022-11-06 10:50:25 -05:00
RandyTheSilly d4d9fb88fd Updated quick-unlock failure error message
Former-commit-id: bd491483ab2b0d09ccfb6c007c786262553d66a7 [formerly 8b8e0c510f]
Former-commit-id: 6a64c402f0687bc3babb0370cd0785a6b2e487f7
2022-11-06 00:49:44 -04:00
RandyTheSilly 2beeb1dc38 Only check whitelist status if the gpg agent is locked (speeds up successive quick-unlock attempts)
Former-commit-id: 57486bc120fc8734cbaf1694655ad9bc14c3276b [formerly 0b0c8a37d1]
Former-commit-id: 171de872a6f4c1f08f151d8b287d1584ac4f7061
2022-11-06 00:43:25 -04:00
RandyTheSilly 86ad4e5cad Added instructions for enabling Bash completions
Former-commit-id: 35b317ff508a4da2c1052cbda230be4e4af42985 [formerly 5fa30fa770]
Former-commit-id: c48091e8a5849359e2a6ef97970d30c008d6a70d
2022-11-06 00:15:28 -04:00
RandyTheSilly e55c812a06 Fixed device id fetching (and quick-unlock verification) on Haiku
Former-commit-id: 2ed945a3fea77e8be59bb2c8c92361cf23f950e8 [formerly 8b3bf04945]
Former-commit-id: 0df6eeb06ab5d68c8252a1b4515895d1eed313ff
2022-11-05 23:57:09 -04:00
RandyTheSilly a0351d19d3 Fixed FreeBSD package symlink target
Former-commit-id: b3d5912d8e12ecea8234ca071cf242fc4b4411cf [formerly 24be16faaa]
Former-commit-id: 2ac9997b4e22f2a9f5d4ffcef75476ce1ae24318
2022-11-05 22:42:53 -04:00
RandyTheSilly 90c15bcda8 Specified changelog location in Fedora package
Former-commit-id: b16bfc43bd29de0ff64d081ec33ffccd86a63e0c [formerly 446cab94c2]
Former-commit-id: d8886c635b20705e8188e14d4bbc8a780e6c1afc
2022-11-05 22:39:28 -04:00
RandyTheSilly cb754c5ba7 Removed executable bits where they are not needed
Former-commit-id: 26ea77aa4734c759aa7e68607250a74ea9d57b71 [formerly e3af035bce]
Former-commit-id: b3acead7183e5c60258b3e0ae9da861f4896f649
2022-11-05 22:33:48 -04:00
RandyTheSilly 9979de26d0 Fixed options/flags layout in man page
Former-commit-id: bcfb14f5de98ae07a743a0d3e3efe2911e594259 [formerly 687a606160]
Former-commit-id: a0c3a8727d5adcc750bfa57305102a23778c42af
2022-11-05 19:40:00 -04:00
RandyTheSilly 4207ecb555 Update 'whitelist del' example to match 'whitelist add' example
Former-commit-id: 16e37f29fe93494f41b82d8bb0bc8cc4f2dc9a59 [formerly e840b9def0]
Former-commit-id: ce1baa1ec004251186d24e228c91608c4641f3ea
2022-11-05 19:32:01 -04:00
RandyTheSilly 8bcfd3fcd8 Fix spacing between exit codes in man page
Former-commit-id: 10708f1512aa4f7aa0d4c410d8d204e27c83eb2f [formerly 486fc39f22]
Former-commit-id: 91a167647b61de66301ab7bf14046637057ec4f3
2022-11-05 19:28:27 -04:00
RandyTheSilly 78588f686e Update whitelist add example in man page to match new string generation
Former-commit-id: dd8e68e426c0a54c038551246877366959bd82fc [formerly a63d1caa2e]
Former-commit-id: d020cf6afd738db53dfde8f0bcd09aedf62b13cc
2022-11-05 19:27:20 -04:00
RandyTheSilly 4753bb4c37 Update synopsis in man page
Former-commit-id: c0b0484f0b221484edda25620844db8414d5e3cd [formerly c2639d1f42]
Former-commit-id: d88298469cb341750174cc9d3c25d2d3ab39056a
2022-11-05 19:22:39 -04:00
RandyTheSilly 81ccdf71ea Fixed visual issue with version info screen
Former-commit-id: 62838cdd5811c70bc60c92803971f4c4e3b2326a [formerly 9cd5066cc9]
Former-commit-id: 326b58542b773f86e333ca6a9f2f07a2c267557a
2022-11-05 19:12:51 -04:00
RandyTheSilly 67eb1a44ca Update documentation for v1.2.0
Former-commit-id: 6068da810b16810c8afc0322cdd26fea734e3671 [formerly 517d2a3d22]
Former-commit-id: a8b3385517fe0f3bc33b7ede1951bae56cd3a265
2022-11-05 18:27:43 -04:00
RandyTheSilly 8af7de10a7 Added a packaging option to create all packages buildable on Arch Linux
Former-commit-id: c8ec6a71c06308ffd9bfc82061c2c8df18c8755f [formerly 80594712c7]
Former-commit-id: e8b9266385f7e2a64de49a863ccb89305f537712
2022-11-05 17:56:14 -04:00
RandyTheSilly 3108a072dd Fixed editing entries with new string generation
Former-commit-id: 957b2fd14635380ebcc1639e60bff0ccb8a7ed13 [formerly ca983d4992]
Former-commit-id: 9e6461835b8e7f2111fa64f5759b88fb00a8cb62
2022-11-05 15:34:19 -04:00
RandyTheSilly 65be0eaf6f Allow complex string generation for /dev/shm and device ids
Former-commit-id: 534577ba9d348aa3bfbce63a940229594a2ae68d [formerly 0614c2c83b]
Former-commit-id: c28b84e388cb9969a881a2a0ab15dfd814875a47
2022-11-05 15:21:30 -04:00
RandyTheSilly bb34025c31 Removed generated password length limit
Former-commit-id: ac2d03db87329e939ef6a0a9494653f53c160348 [formerly 900f216ac0]
Former-commit-id: ad17b1a2d026ab6ee221dfb260acb6a8a79200de
2022-11-05 14:01:49 -04:00
RandyTheSilly 278231fe40 Several fixes to allow the Haiku package to work again
Former-commit-id: a1babda0d16b3a264a53936fd76f79d00b56976a [formerly 022ad9ef6d]
Former-commit-id: a3a64fe962fd866bae3b5a8293293f9bad211994
2022-11-04 00:30:48 -04:00
RandyTheSilly 7356777e50 Made spacing in server help menus consistent with client help menus
Former-commit-id: 6b5209753e1a4d91de9f058bd4003def53b9e44c [formerly a1d5c8607c]
Former-commit-id: f1dc1e65d8eb65b58c87be822e1540e6862fce57
2022-11-03 22:58:35 -04:00
RandyTheSilly 5a33eb26f0 Fedora now only requires openssh-clients, rather than the openssh meta package
Former-commit-id: 320ec033563f9bf6d246c82ba504ee29085dfb5f [formerly 942febfac9]
Former-commit-id: ec41cf2e719ad9127ed84322f91248075e52ccbc
2022-11-03 22:47:58 -04:00
RandyTheSilly d796c48ede Made exit codes more specific to their errors
Former-commit-id: 62c004c84333a8b8645ea3247fdb56c1509a36f9 [formerly 33f7330886]
Former-commit-id: 0d849ab1792d4c960cf632f75a28feba5610f57e
2022-10-26 13:32:23 -04:00
RandyTheSilly b1fd934b96 Added bash-completion as an optional dependency where applicable
Former-commit-id: 2ad3b6bf53ac4c211a3e06079e0b32d7c362949f [formerly 07394c1740]
Former-commit-id: a985cf7c1230bc4f0f821ad83e689ddd769ba8d0
2022-10-25 23:10:51 -04:00
RandyTheSilly 9848d38a0b Fixed quick-unlock requirng passphrase regardless of whether or not it is needed - again
Former-commit-id: 53325cbf8c6f46d4e83c4e9ed4b9f9d4b1196bd5 [formerly a26ca86977]
Former-commit-id: 21fb1f899370f428ea08e1e5bb50bc2610e00983
2022-10-25 11:53:52 -04:00
RandyTheSilly 2ab409e95e Require _client_device_id as parameter to whitelist_verify (for extension support)
Former-commit-id: 608cd556ac34c6d64a0559398a43c180e41e1510 [formerly 51d99992a5]
Former-commit-id: d0e316a16321a13972be7233c88ffb61d5f5c3f7
2022-10-24 15:31:06 -04:00
RandyTheSilly 64d46f1746 Initial enablement of extension support for quick-unlock
Former-commit-id: 91400c4725bf786d727695287ec122c01d2447da [formerly d887047fd0]
Former-commit-id: 9576649c2d5f2de0fd6a8cfa5a92afae45304f45
2022-10-20 15:25:57 -04:00
RandyTheSilly e9c787cfe3 Removed an exit statement that was preventing extensions from working
Former-commit-id: 1f89608ad6b7a6a65aca87742991f9195937d8a1 [formerly 304c1b09f0]
Former-commit-id: 8856ed6d9f72fd72f7a91d35dd987f00f1f8eaaf
2022-10-19 15:13:14 -04:00
RandyTheSilly c200b5bbec Allow fallback to standard unlock if quick-unlock fails
Former-commit-id: b6bfe67cdd983da31366ec45c7f5950f856d0dfe [formerly 6b20b82dd5]
Former-commit-id: f685be2b3e57f396ca41a8d2d1e3382a0f978990
2022-10-19 11:34:02 -04:00
RandyTheSilly b6593e609b Created local _quick_enabled for decrypt() for extension support
Former-commit-id: 996ed134188de1425a4ffb0f1bb741fd38b25ab1 [formerly e66ecd9514]
Former-commit-id: 1792c36fd44437f22ad1f86cedcf1c66083715cc
2022-10-19 10:14:39 -04:00
RandyTheSilly 32b0c39fb6 Removed need to re-enter quick-unlock passphrase is the gpg agent is already unlocked
Former-commit-id: e1c94c5f40e1ad16f710c11008c22ce353e50ade [formerly c8691372ca]
Former-commit-id: 94164815b1ee10e3a99c5c48f288f86b751b2449
2022-10-19 10:04:52 -04:00
RandyTheSilly 6b8e24b0b2 Optimized decrypt()
Former-commit-id: 8ad01f00ecf678ea8716507745791818343eaf66 [formerly 2b98562dca]
Former-commit-id: fdc02b6db471b98fbbdf55ddc381a25b68477f0b
2022-10-19 09:33:38 -04:00
RandyTheSilly 2d47f42414 Fixed string_gen complex generating unprintable strings
Former-commit-id: beec4dc72264edf0a2818a5088c849a310d5f7d0 [formerly 9d990eb9f6]
Former-commit-id: 09895d9c665ece34d74c5c68ce6657952c19ca59
2022-10-18 14:54:10 -04:00
RandyTheSilly a773056202 Fixed shearing and renaming entries with quick-unlock
Former-commit-id: f2e3558a0f923d0b5082987858d0e0e59f820f7d [formerly 07cf7a558f]
Former-commit-id: f5036da37b69cb339aba710aa53c9bbf9853567a
2022-10-18 13:35:12 -04:00
RandyTheSilly 097aca8c43 Automatically set quick-unlock passphrase length if the user specifies an invalid option
Former-commit-id: 0343f407b9c9bae5d68af1a0d1cd8028ff9b09be [formerly fa7e03bcf7]
Former-commit-id: dd454b8b47747b556d3268eaf6a38bda5a6a2627
2022-10-13 16:49:08 -04:00
RandyTheSilly 275b07e53f Removed unnecessary s_exit() statements
Former-commit-id: ab2a098f5d620ad8b25665a55e0051c2509e9c99 [formerly f5161ef5cb]
Former-commit-id: 6842c72fb31ab57fc59e0643a332d66b24ab050c
2022-10-13 10:58:07 -04:00
RandyTheSilly 46d539f386 Made error message for when quick-unlock fails more clear
Former-commit-id: 2acc2f84b9205e32cf5bd6bda23abb576002c2a9 [formerly fd12a3c003]
Former-commit-id: 14dc80d8c7c22baed913a0c84abdb3c67138bef9
2022-10-12 16:24:27 -04:00
RandyTheSilly 0adf00d81e Respect the user-specifed quick-unlock passphrase length
Former-commit-id: 5ef20818df71d226d14bf97d99d8646b09a0a7b6 [formerly 70ee2c6d04]
Former-commit-id: 65b4c531f2e84aa5198e1c77a1d0624482cd70e0
2022-10-12 14:57:33 -04:00
RandyTheSilly 46b212988f Truncate max quick-unlock password length
Former-commit-id: c405a82ba419a61bc6d65e89315c8306cec3f01e [formerly 35ee78f717]
Former-commit-id: 53d536d2c37dfca60fd154a90e7857fd1204fc35
2022-10-12 14:53:48 -04:00
RandyTheSilly 3e8772786a Fixed failed decryption on excluded.gpg
Former-commit-id: 8bd59bcf06d63daad654f9ddea9e5ccbcf1b2ea5 [formerly 8dd5101c7f]
Former-commit-id: 6b4e01337c3a0a337a558384283a467eb9e7de1c
2022-10-12 14:37:54 -04:00
RandyTheSilly 3a01157cb3 Fixed quick-unlock gpg key passphrase creation and made quick-unlock more verbose
Former-commit-id: 88dc9cc46cbdaaa3fc547b81758892ebc463573d [formerly 844280c13e]
Former-commit-id: 454e554a4009ed89d3413d57662cde7cca7d9289
2022-10-12 13:39:01 -04:00
RandyTheSilly 079a1412ae Attempt remote imports from /lib/sshyp, rather than /bin
Former-commit-id: 839058d4882b8c8837b4543e6b290643d9f1d409 [formerly d9e2a7d4e1]
Former-commit-id: eeb4a7bb40356e453a41e0176e40d08c6056a7c1
2022-10-12 12:48:08 -04:00
RandyTheSilly 359edcd46c Ensure generated device id is a valid file name
Former-commit-id: 950080785d4666364b5452a4e0b48ce999852ef8 [formerly 3147d9327b]
Former-commit-id: da5d01ac15b077cb2d63668ee9be491ef597a105
2022-10-12 12:32:54 -04:00
RandyTheSilly 60c2920f3c Fixed quick-unlock configuration looking for an integer as input
Former-commit-id: 4a87cd897e7cc5f480d4b68c809ee794af88b7eb [formerly 29c7b931c9]
Former-commit-id: 49fc70a301120b8c7221ec10328186f41ef7294c
2022-10-12 12:19:02 -04:00
RandyTheSilly a3cd6a1f17 Implemented quick-unlock decryption support
Former-commit-id: e09eafab15796554dd5ae4013834c68a99b1df8a [formerly b81018c122]
Former-commit-id: 6a539e12d1ff63e29ba551b3b4622c4022673ff0
2022-10-12 12:06:07 -04:00
RandyTheSilly 8c2b7df1da Simplify client-side quick-unlock configuration
Former-commit-id: efb36f511a0d12172d0274c10f4000ce0b3822fa [formerly 5a9457cc5d]
Former-commit-id: fd65329c7834164054de79f6d0889c2a3957b7d9
2022-10-12 11:46:12 -04:00
RandyTheSilly 994eaee49b Initial implementation of quick-unlock verification
Former-commit-id: a71a8d0a7a1964597e5c52889b8f89117ba82ab9 [formerly 845f968aeb]
Former-commit-id: e53a2994ca43ac3a82c0cdeb88e74c180c20d85a
2022-10-11 17:08:30 -04:00
RandyTheSilly 8f0eb1134a Properly increment _i in whitelist_setup()
Former-commit-id: afced727ea431a420d43780f417e991c3457641c [formerly 9b0b4acf77]
Former-commit-id: 1d5bb9e6359e5a39d49764d8d920d776532f8de6
2022-10-11 12:42:42 -04:00
RandyTheSilly c95643ca89 Added server-side whitelist setup
Former-commit-id: 5cfa4acfc90eaa977feec5ddc427bc5292b228d7 [formerly c9934368a4]
Former-commit-id: 266b7c430437bf807de97833b20c7047058b332d
2022-10-11 12:40:35 -04:00
RandyTheSilly 35ea8bf7d7 Reduced questions asked for quick-unlock setup
Former-commit-id: 0903f70d8924df22eb5c09ff25a8adfd81bf7859 [formerly 243c3c466e]
Former-commit-id: 65d4a73935b03cb9eef41689e3e8f82b85e62ebf
2022-10-11 10:51:00 -04:00
RandyTheSilly 60431b623c Fixed decryption of multi-word entries
Former-commit-id: 3175538b5d759e29a70718fa10950d68e1822014 [formerly 40213aa518]
Former-commit-id: 9da31c6b23689091ad1be58cd71aab0c8c6bb379
2022-10-11 09:47:24 -04:00
RandyTheSilly 185ae4ebff Move a rarely used import statement closer to where it's needed in sshync
Former-commit-id: aceb7e080b29fedf9e8a52ab052af32450491b88 [formerly c31a5a1664]
Former-commit-id: 9844be47e81146aff688e7e1c8462ffc147ab29d
2022-10-11 09:28:13 -04:00
RandyTheSilly 1e290a1f26 Only import rarely used modules when they're needed
Former-commit-id: 1ec2f9c6884f0e06b32d693c38ffe5c698b7dc52 [formerly 8642601eee]
Former-commit-id: d5078e981c24350843ec99597cbbfed619f555bf
2022-10-11 09:22:40 -04:00
RandyTheSilly 71157633df Fixed renaming folders and receiving false error messages when renaming entries in offline mode
Former-commit-id: 49f2fef8ea219945446347df3d782c0660429a05 [formerly 71585770eb]
Former-commit-id: 5e956861f47581704d85bfee5d52728be784d6b3
2022-10-11 09:10:31 -04:00
RandyTheSilly 03487348ea Fixed deleting and renaming entries in offline mode
Former-commit-id: eb690eae6213b2863ac9f93c2fecacb1f77128fd [formerly affcb3adbe]
Former-commit-id: 15fc77ab3728b8f40f8d33bbd6df759fdc7c1283
2022-10-10 19:37:38 -04:00
RandyTheSilly 2d20ddbb97 Fixed folder creation in offline mode
Former-commit-id: 1669541a827e1f6c68e28e738778a131d56645c5 [formerly 45605bf7bc]
Former-commit-id: 62aa51979453eaa9fea50766cbab9fd968c5e65f
2022-10-10 18:20:43 -04:00
RandyTheSilly 21287ca57f Fixed bugs causing entries to not be saved or entries being saved to the wrong place
Former-commit-id: b8ff6c48caefdd051bc54705a9c08bf41add925a [formerly 772eaa0846]
Former-commit-id: 1700df7af1a9b1ce30ca881e8bfc5f6c42ed1666
2022-10-10 17:52:14 -04:00
RandyTheSilly 612f288904 Put .py files in /usr/lib/sshyp, symlink executables to /usr/bin
Former-commit-id: c44a817f58d2f232d93c2d38ca27c4b091a3c523 [formerly c9c88a041d]
Former-commit-id: cb19027d8aa9a1ea97a02bfaf3b41292f5a3f232
2022-10-10 11:16:56 -04:00
RandyTheSilly 0fbe084ff7 Correctly add strings to config file list
Former-commit-id: 7defa79968068b45f64deb0f7df183650ce111b5 [formerly cc4324f953]
Former-commit-id: a431ad4d3e40a85658d59b03455fbabc0667e41e
2022-10-08 21:33:35 -04:00
RandyTheSilly fdf90e043a Overwrite old configuration file
Former-commit-id: ca68d65edcb90d827ee8588d4c251e8bfb35fac6 [formerly 48170f494e]
Former-commit-id: 68c2175fea457766e0c0f8d2fe5751fc2812dbe0
2022-10-08 21:19:47 -04:00
RandyTheSilly b89158d0bb Bug fixes for the revised config file format and server-side arguments
Former-commit-id: 7a924b6a74a488a1116f341110d2bda73ea37cca [formerly 324a5f9a7c]
Former-commit-id: f24a5a8a859fa603b721f888393b25fe944d698e
2022-10-08 21:10:47 -04:00
RandyTheSilly dede7cf8ac Initial addition of offline mode
Former-commit-id: 8bd18e3b3ba4d5d737c25416632ba13a6a38c0ef [formerly 2c1241a1bc]
Former-commit-id: fdb675335dd16ed90cf1deb439a665ba2b3621bb
2022-10-06 16:42:58 -04:00
RandyTheSilly 6d3ba2714b Client-side whitelist error message now shows up will all additional whitelist options
Former-commit-id: c67567cdfd1881a10a39a3ab2b8deb27d9bed01c [formerly d069ab82f9]
Former-commit-id: 70339b78b1d65b2850c9deb431419eb6c9bf4eff
2022-10-06 09:02:32 -04:00
RandyTheSilly 09991a2a30 Added error message for when whitelist is used client-side
Former-commit-id: 70e01336cf53c78905784e6ec730d0c8f542d410 [formerly b8b99755f0]
Former-commit-id: 82f626a998106db61f101b21f2d1f1479eb604cd
2022-10-06 08:57:48 -04:00
RandyTheSilly c5884024de Use rstrip() in place of replace() where applicable
Former-commit-id: 6b0a80036ec8166663dfdbd02c720f4b5ed23064 [formerly 88e97d0131]
Former-commit-id: 1e861149b7485ef6676ce4c5b721dd4ea0d8ba90
2022-10-06 08:47:38 -04:00
RandyTheSilly 1e2166ddaf Added quick-unlock configuration, tweaked config files
Former-commit-id: bc51fe8e937af4b1723af804965eae0e2e0a4ed4 [formerly 012796f052]
Former-commit-id: 52db53b8633894dd5d0f3d5a0abfa4aeede41c61
2022-10-05 23:46:32 -04:00
RandyTheSilly 86487b8ae5 Rename 'device name' to 'device id'
Former-commit-id: 35521f185ee50203d24dab3e3ae84cf8930eb7e7 [formerly 0a450018de]
Former-commit-id: 783fab372f69f811417dd4f7be6269aaff7f15aa
2022-10-04 23:47:19 -04:00
RandyTheSilly fa05312d49 Whitelist bugfixes and polish
Former-commit-id: fa8991e443fb5117d1a69afa4cd0b0017bacda1f [formerly 773f27946e]
Former-commit-id: e417225d361e3fdb1021885336ec7a9f86e0fdaa
2022-10-04 22:50:05 -04:00
RandyTheSilly 71d942ffae Initial quick-unlock whitelist implementation
Former-commit-id: 0ec410451bf7799bd1c603e8ebc25401efe2850b [formerly 5f7074e04e]
Former-commit-id: ce55288649a10ef7cb20ad2dc1c815a13d263988
2022-10-04 21:08:11 -04:00
RandyTheSilly cc1a099363 Optimized device type setup
Former-commit-id: 3e7b61794bf27e88f0cee1cb3771ee474ef1df70 [formerly 2a56a3c427]
Former-commit-id: 20568fbd2f980319b9a167c3142dc6ee5ca87a6e
2022-10-04 15:12:14 -04:00
RandyTheSilly d6342faf84 Properly detect whitelist argument
Former-commit-id: 5bea6aa37ae4356de2d64d077da7c7f60eba3aa3 [formerly a573778972]
Former-commit-id: be5c0832c95991b9103c7ad97f13d76e60b61dba
2022-10-04 15:03:27 -04:00
RandyTheSilly c7cae5303a Add dedicated server help menu
Former-commit-id: 36a139597b19d1c5ad9c08424723069d72221b11 [formerly c009dd63ab]
Former-commit-id: 46227a7c5aad5aff8ecfd8529bc53e433b3a57a8
2022-10-04 14:37:04 -04:00
RandyTheSilly 1d80cb08ce Allow some arguments when running as server
Former-commit-id: c4d50678ff19fe452eb06de70e3890e919e0d0b2 [formerly aadca0ad44]
Former-commit-id: 21750ce3902b7e8bcc50b1068198c4a329c3912b
2022-10-04 13:42:21 -04:00
RandyTheSilly 8163aee868 Swap URL and note options in edit help menu for visual consistency
Former-commit-id: 974a7cbb5a21e9b093c690ef67621e41de16bfdd [formerly 6ffc65f0d0]
Former-commit-id: de6cc2e91778ac13a193e4d77edb297cb8dd543c
2022-10-04 13:25:31 -04:00
RandyTheSilly 7c061f561f Removed metainfo file, as it did not solve the issue with Debian licesning
Former-commit-id: 2a9ee72c9e6dca49ef7a95d62902237def77969f [formerly 8980bef8ce]
Former-commit-id: 11313daff2acf3980925e45776a88f7f40aff964
2022-10-04 13:18:25 -04:00
RandyTheSilly 339d20bc3e Update license to GPL-3.0-only
Former-commit-id: 89163bb523391768dcfdee8c9873ab5443cd66d0 [formerly 1436cdfb90]
Former-commit-id: 3a8ce9b0569a32839344a9fdfd664af28ce1fe07
2022-10-04 12:44:39 -04:00
RandyTheSilly 434f5f85cd Added metainfo.xml file for AppStream metadata
Former-commit-id: ad6f791e8c5c7036f70de16125e016cb9e7e40a3 [formerly 374c2336c8]
Former-commit-id: c855d50011ca4107fd007cf693575cff1178beb4
2022-10-04 11:44:35 -04:00
RandyTheSilly 22d9605781 Update README.md
Former-commit-id: 943e9e1f7daedddaaa861f4845118fb8db4c7803 [formerly 5c58151888]
Former-commit-id: 2925645b0f261b98a738240fcb6abec83622f8f7
2022-09-21 11:24:12 -04:00
RandyTheSilly d644f8df01 Initial implementation of non-interactive packaging script
Former-commit-id: 92c811513a6190f7d0f9e479e84bd54eb7a13556 [formerly 7e29a9ba9e]
Former-commit-id: 5fe414bb4dfbe286b53845826c9bdcf20ab5b22f
2022-09-12 15:21:55 -04:00
RandyTheSilly 551789636d Slight line count reduction
Former-commit-id: 442835aaecb34b116ca8a44109e08a0643344271 [formerly b27f4cb805]
Former-commit-id: 1087b9aae0e2c0e641e7f44e901ac16e07a33270
2022-09-08 10:19:43 -04:00
RandyTheSilly 9ab175a8d8 Roughly halved the amount of time to source bash completions
Former-commit-id: 624a99a5727e49b12c1468c27110cee893124fd2 [formerly f411a56d1f]
Former-commit-id: 48802e27abb02efaf80743bd4fc8dbe3a03219fa
2022-09-08 09:47:59 -04:00
RandyTheSilly e5a4d80e3b Packaging script shebang changed to "/usr/bin/env bash"
Former-commit-id: 6d7053b0f6e1e3aec1373d664acfb3fc71c00cc9 [formerly 6542e8f7e2]
Former-commit-id: f9506fab60e02e0f402ea246feab5c5ae8d77888
2022-09-07 09:46:09 -04:00
RandyTheSilly 0920dcf004 Fixed completions not generating
Former-commit-id: 3f8c64047f12456fd3c1ce8243293beb323912a2 [formerly 915a17b8e9]
Former-commit-id: 0e322f3cda6e75f2db8b9e649028fccbf1ae09ad
2022-09-05 14:29:43 -04:00
RandyTheSilly e18b3b4d0a Allow path completions alongside shortcuts
Former-commit-id: 8404cf01dd8b828b69d85bdbe520ad422a47b1ae [formerly d037acd5b0]
Former-commit-id: ae522f2d127096c5654748a53f4bffef7e234609
2022-09-04 15:10:04 -04:00
RandyTheSilly ffccc88a1d The _path_gen function is no longer run twice (completion is much faster now)
Former-commit-id: 996add6978d32e2a1d72b12352baa682645b3061 [formerly 2ed8b69abf]
Former-commit-id: 2e90025a3d6aaba8ff1987cb3731727a15d5b2e4
2022-09-04 14:55:39 -04:00
RandyTheSilly e1ec834e5c Added initial Bash completion script
Former-commit-id: 81ff7f4a6e515e36cea79953a0212bfd3f89958e [formerly 510ca3bfbb]
Former-commit-id: a85ddda326e5d210ce47bc03a668e3f4ba3537c9
2022-09-04 14:49:43 -04:00
13 changed files with 1539 additions and 1125 deletions
Executable → Regular
View File
Executable → Regular
+13 -9
View File
@@ -1,7 +1,13 @@
![sshyp](https://github.com/rwinkhart/sshyp-labs/blob/main/extra/artwork/sshyp-banner.png)
[![release](https://img.shields.io/github/v/release/rwinkhart/sshyp)](https://github.com/rwinkhart/sshyp/releases)
![python](https://img.shields.io/badge/python-3.7--3.11-yellow)
[![downloads](https://img.shields.io/github/downloads/rwinkhart/sshyp/total)](https://github.com/rwinkhart/sshyp/releases)
[![CodeQL](https://github.com/rwinkhart/sshyp/actions/workflows/codeql-analysis.yml/badge.svg?branch=main)](https://github.com/rwinkhart/sshyp/actions/workflows/codeql-analysis.yml)
pronounced as: 'sheep', 'shēp'
sshyp is a very simple self-hosted, synchronized password manager for UNIX(-like) systems (currently Haiku/FreeBSD/Linux).
sshyp is compatible with entries created by pass/password-store, as its original goal was to be like pass/password-store, but far more user-friendly to synchronize with a self-hosted server.
@@ -24,15 +30,11 @@ What sshyp can do:
- securely manage a collection of encrypted passwords and notes via CLI
- generate new, secure passwords to the user's choice in length and complexity
- securely sync said passwords and notes seamlessly between devices
- utilize [extensions](https://github.com/rwinkhart/sshyp-labs) to interact with your entries is additional ways (such as generating TOTP keys)
- securely sync said passwords and notes seamlessly between devices (or just manage them offline)
- utilize [extensions](https://github.com/rwinkhart/sshyp-labs) to interact with your entries is additional ways (such as generating TOTP keys or managing your entries in a GUI)
- everything above with entries created by pass/password-store!
- everything above on Haiku, FreeBSD, Linux, and Termux (an Android terminal emulator)!
What sshyp will likely do:
- (planned v1.2.0) everything it already does, but also in a GUI for Linux/Haiku
What sshyp definitely won't do:
- Non-UNIX(-like) support, e.g. Windows (I'd be happy to link to third-party ports, if someone were to make them)
@@ -46,6 +48,8 @@ Requests for additional distribution/OS support can be filed as issues.
Extensions are available in the [sshyp-labs](https://github.com/rwinkhart/sshyp-labs) repository.
Bash completions can be enabled by installing your distribution's "bash-completion" package and restarting your terminal.
# Building
Since sshyp is written entirely in Python, it doesn't need to be compiled. It does, however, need to be packaged for installation.
@@ -54,7 +58,7 @@ A packaging script is included in the root directory of the repo in order to pac
```
git clone https://github.com/rwinkhart/sshyp.git
cd sshyp
./package.sh
./package.sh [target] <revision>
```
The packaging script has been tested on Arch Linux with "dpkg" as a dependency for Debian/Ubuntu and Termux packaging and "freebsd-pkg" as a dependency for FreeBSD packaging.
@@ -91,8 +95,8 @@ man sshyp
# Roadmap
Short-term Goals:
- create minimal GUI app (Linux x86_64, Linux aarch64)
- various optimizations/bug fixes
- a minimal GUI app - being made as an [extension](https://github.com/rwinkhart/sshyp-labs)
- improved extension integration (allow extensions to add new arguments)
Long-term Goals:
-104
View File
@@ -1,104 +0,0 @@
#!/usr/bin/env python3
# external modules
from os import path, system, walk
from os.path import expanduser, getmtime, join
from pathlib import Path
from sys import exit as s_exit
# utility functions
def get_titles_mods(_directory, _destination, _user_data): # fetches and returns lists of titles and their mod times
_title_list, _mod_list = [], []
Path(path.expanduser('~/.config/sshync')).mkdir(0o700, parents=True, exist_ok=True) # create config directory
# local fetching
if _destination == 'l':
open(expanduser('~/.config/sshync/database'), 'w').write('') # blanks out database file
for _root, _directories, _files in walk(_directory):
for _filename in _files:
_title_list.append(join(_root.replace(_directory, '', 1), _filename))
_mod_list.append(int(getmtime(join(_root, _filename))))
for _title in _title_list:
open(expanduser('~/.config/sshync/database'), 'a').write(str(_title) + '\n')
open(expanduser('~/.config/sshync/database'), 'a').write('^&*\n')
for _mod in _mod_list:
open(expanduser('~/.config/sshync/database'), 'a').write(str(_mod) + '\n')
# remote fetching
if _destination == 'r':
system(f"ssh -i '{_user_data[5]}' -p {_user_data[2]} {_user_data[0]}@{_user_data[1]} \"cd /bin; python -c 'impo"
f"rt sshync; sshync.get_titles_mods(\"'\"{_user_data[4]}\"'\", \"'\"l\"'\", \"'\"{_user_data}\"'\")'\"")
system(f"scp -pqs -P {_user_data[2]} -i '{_user_data[5]}' {_user_data[0]}@{_user_data[1]}:"
f"'/home/{_user_data[0]}/.config/sshync/database' '{expanduser('~/.config/sshync/')}'")
_titles, _sep, _mods = '*&^'.join(open(expanduser('~/.config/sshync/database')).readlines()).replace('\n', '')\
.partition('^&*')
_title_list, _mod_list = _titles.split('*&^')[:-1], _mods.split('*&^')[1:]
return _title_list, _mod_list
def sort_titles_mods(_list_1, _list_2): # creates and returns two lists (of titles and mod times) generated by sorting
# information from two different, provided lists
_title_list_2_sorted, _mod_list_2_sorted = [], []
# title sorting
for _title in _list_1[0]:
if _title in _list_2[0]:
_title_list_2_sorted.append(_title)
for _title in _list_2[0]:
if _title not in _title_list_2_sorted:
_title_list_2_sorted.append(_title)
# mod time sorting
for _title in _title_list_2_sorted:
_mod_list_2_sorted.append(_list_2[1][_list_2[0].index(_title)])
return _title_list_2_sorted, _mod_list_2_sorted
def make_profile(_profile_dir, _local_dir, _remote_dir, _identity, _ip, _port, _user): # creates a sshync job profile
open(_profile_dir, 'w').write(_user + '\n' + _ip + '\n' + _port + '\n' + _local_dir + '\n' + _remote_dir + '\n' +
_identity + '\n')
def get_profile(_profile_dir): # returns a list of data read from a sshync job profile
try:
_profile_data = open(_profile_dir).readlines()
except (FileNotFoundError, IndexError):
print('\n\u001b[38;5;9merror: the profile does not exist or is corrupted.\u001b[0m\n')
_profile_data = None
s_exit(1)
# extract data from profile
_user = _profile_data[0].replace('\n', '')
_ip = _profile_data[1].replace('\n', '')
_port = _profile_data[2].replace('\n', '')
_local_dir = _profile_data[3].replace('\n', '')
_remote_dir = _profile_data[4].replace('\n', '')
_identity = _profile_data[5].replace('\n', '')
return _user, _ip, _port, _local_dir, _remote_dir, _identity
def run_profile(_profile_dir): # runs a sshync job profile
_user_data = get_profile(_profile_dir)
_remote_titles_mods_saver = get_titles_mods(_user_data[4], 'r', _user_data) # saved to prevent re-walking directory
_index_l = sort_titles_mods(_remote_titles_mods_saver, get_titles_mods(_user_data[3], 'l', _user_data))
_index_r = sort_titles_mods(_index_l, _remote_titles_mods_saver)
_i = -1
for _title in _index_l[0]:
_i += 1
if _title in _index_r[0]:
# compare mod times and sync
if int(_index_l[1][_i]) > int(_index_r[1][_i]):
print(f"\u001b[38;5;4m{_title[:-4]}\u001b[0m is newer locally, uploading...")
system(f"scp -pqs -P {_user_data[2]} -i '{_user_data[5]}' '{_user_data[3]}{_title}' "
f"'{_user_data[0]}@{_user_data[1]}:{_user_data[4]}{'/'.join(_title.split('/')[:-1]) + '/'}'")
elif int(_index_l[1][_i]) < int(_index_r[1][_i]):
print(f"\u001b[38;5;2m{_title[:-4]}\u001b[0m is newer remotely, downloading...")
system(f"scp -pqs -P {_user_data[2]} -i '{_user_data[5]}' '{_user_data[0]}@{_user_data[1]}:"
f"{_user_data[4]}{_title}' '{_user_data[3]}{'/'.join(_title.split('/')[:-1]) + '/'}'")
else:
print(f"\u001b[38;5;4m{_title[:-4]}\u001b[0m is not on remote server, uploading...")
system(f"scp -pqs -P {_user_data[2]} -i '{_user_data[5]}' '{_user_data[3]}{_title}' "
f"'{_user_data[0]}@{_user_data[1]}:{_user_data[4]}{'/'.join(_title.split('/')[:-1]) + '/'}'")
for _title in _index_r[0]:
if _title not in _index_l[0]:
print(f"\u001b[38;5;2m{_title[:-4]}\u001b[0m is not in local directory, downloading...")
system(f"scp -pqs -P {_user_data[2]} -i '{_user_data[5]}' '{_user_data[0]}@{_user_data[1]}:"
f"{_user_data[4]}{_title}' '{_user_data[3]}{'/'.join(_title.split('/')[:-1]) + '/'}'")
-765
View File
@@ -1,765 +0,0 @@
#!/usr/bin/env python3
# external modules
from os import environ, listdir, path, remove, system, uname, walk
from pathlib import Path
from random import randint, SystemRandom
from shutil import get_terminal_size, move, rmtree
import sshync
import string
from subprocess import CalledProcessError, Popen, PIPE, run
from sys import argv, exit as s_exit
from textwrap import fill
# BELOW - utility functions
def entry_list_gen(_directory=path.expanduser('~/.local/share/sshyp/')): # generates and prints full entry list
print('\n\u001b[38;5;0;48;5;15msshyp entries:\u001b[0m\n')
_entry_list, _color_alternator = [], 1
for _entry in sorted(listdir(_directory)):
if Path(f"{_directory}{_entry}").is_file():
if _color_alternator == 1:
_entry_list.append(f"{_entry.replace('.gpg', '')}")
_color_alternator = 2
else:
_entry_list.append(f"\u001b[38;5;8m{_entry.replace('.gpg', '')}\u001b[0m")
_color_alternator = 1
_real = len(' '.join(_entry_list)) - (5.5 * len(_entry_list))
if _real <= get_terminal_size()[0]:
_width = len(' '.join(_entry_list))
else:
_width = (len(' '.join(_entry_list)) / (_real / get_terminal_size()[0]) - 25)
try:
print(fill(' '.join(_entry_list), width=_width) + '\n')
except ValueError:
pass
for _root, _directories, _files in walk(_directory):
for _dir in sorted(_directories):
_inner_dir = f"{_root.replace(_directory, '')}/{_dir}"
print(f"\u001b[38;5;15;48;5;238m{_inner_dir}/\u001b[0m")
_entry_list, _color_alternator = [], 1
for _s_root, _s_directories, _s_files in walk(f"{_directory[:-1]}{_inner_dir}"):
for _entry in sorted(_s_files):
if _color_alternator == 1:
_entry_list.append(f"{_entry.replace('.gpg', '')}")
_color_alternator = 2
else:
_entry_list.append(f"\u001b[38;5;8m{_entry.replace('.gpg', '')}\u001b[0m")
_color_alternator = 1
_real = len(' '.join(_entry_list)) - (5.5 * len(_entry_list))
if _real <= get_terminal_size()[0]:
_width = len(' '.join(_entry_list))
else:
_width = (len(' '.join(_entry_list)) / (_real / get_terminal_size()[0]) - 25)
try:
print(fill(' '.join(_entry_list), width=_width) + '\n')
except ValueError:
print('\u001b[38;5;9m-empty directory-\u001b[0m\n')
def entry_reader(_decrypted_entry): # displays the contents of an entry in a readable format
_entry_lines, _notes_flag = open(_decrypted_entry, 'r').readlines(), 0
print()
for _num in range(len(_entry_lines)):
try:
if _num == 0 and _entry_lines[1] != '\n':
print(f"\u001b[38;5;15;48;5;238musername:\u001b[0m\n{_entry_lines[1].strip()}\n")
elif _num == 1 and _entry_lines[0] != '\n':
print(f"\u001b[38;5;15;48;5;238mpassword:\u001b[0m\n{_entry_lines[0].strip()}\n")
elif _num == 2 and _entry_lines[2] != '\n':
print(f"\u001b[38;5;15;48;5;238murl:\u001b[0m\n{_entry_lines[_num].strip()}\n")
elif _num >= 3 and _entry_lines[_num] != '\n' and _notes_flag != 1:
_notes_flag = 1
print(f"\u001b[38;5;15;48;5;238mnotes:\u001b[0m\n{_entry_lines[_num].strip()}")
elif _num >= 3 and _notes_flag == 1:
print(_entry_lines[_num].replace('\n', ''))
if _notes_flag == 1:
try:
_line_test = _entry_lines[_num + 1]
except IndexError:
print()
except IndexError:
if _num == 0:
print(f"\u001b[38;5;15;48;5;238mpassword:\u001b[0m\n{_entry_lines[0]}\n")
def entry_name_fetch(_entry_name_location): # fetches and returns entry name from user input or from provided argument
if type(_entry_name_location) == str:
entry_list_gen()
_entry_name = str(input(_entry_name_location))
else:
_entry_name_split = argument.split(' ')
del _entry_name_split[:_entry_name_location]
_entry_name = ' '.join(_entry_name_split)
if _entry_name.startswith('/'):
return _entry_name.replace('/', '', 1)
else:
return _entry_name
def shm_gen(_tmp_dir=path.expanduser('~/.config/sshyp/tmp/')): # creates a temporary directory for entry editing
_shm_folder_gen = ''.join(SystemRandom().choice(string.ascii_letters + string.digits)
for _ in range(randint(10, 30)))
_shm_entry_gen = ''.join(SystemRandom().choice(string.ascii_letters + string.digits)
for _ in range(randint(10, 30)))
Path(_tmp_dir + _shm_folder_gen).mkdir(0o700)
return _shm_folder_gen, _shm_entry_gen
def pass_gen(): # generates and returns a random password based on user-specified options
def _pass_gen_function(__complexity, __length):
if __complexity.lower() == 's':
__character_pool = string.ascii_letters + string.digits
else:
__character_pool = string.ascii_letters + string.digits + string.punctuation
__gen = ''.join(SystemRandom().choice(__character_pool) for _ in range(__length))
__min_special, __special = round(.2 * __length), 0
for __character in __gen:
if not __character.isalpha():
__special += 1
if __special < __min_special:
__gen = _pass_gen_function(__complexity, __length)
return __gen
try:
_length = int(input('password length: '))
except ValueError:
print(f"\n\u001b[38;5;9merror: a non-integer value was input for password length\u001b[0m\n")
_gen = pass_gen()
return _gen
if _length > 840:
_length = 840
print('\n\u001b[38;5;9mpassword length has been limited to the maximum of 840 characters\u001b[0m\n')
_complexity = str(input('password complexity - simple (for compatibility) or complex (for security)? (s/C) '))
_gen = _pass_gen_function(_complexity, _length)
return _gen
def encrypt(_entry_dir, _shm_folder, _shm_entry, _gpg_com, _gpg_id, _tmp_dir=path.expanduser('~/.config/sshyp/tmp/')):
# encrypts an entry and cleans up the temporary files
system(f"{_gpg_com} -qr {str(_gpg_id)} -e '{_tmp_dir}{_shm_folder}/{_shm_entry}'")
move(f"{_tmp_dir}{_shm_folder}/{_shm_entry}.gpg", f"{_entry_dir}.gpg")
rmtree(f"{_tmp_dir}{_shm_folder}")
def decrypt(_entry_dir, _shm_folder, _shm_entry, _gpg_command, _tmp_dir=path.expanduser('~/.config/sshyp/tmp/')):
# decrypts an entry to a temporary directory
if _shm_folder == 0 and _shm_entry == 0:
_command = f"{_gpg_command} -qd --output /dev/null {path.expanduser('~/.config/sshyp/lock.gpg')}"
else:
_command = f"{_gpg_command} -qd --output {_tmp_dir}{_shm_folder}/{_shm_entry} {_entry_dir}.gpg"
try:
run(_command, shell=True, stderr=PIPE, check=True, close_fds=True)
except CalledProcessError:
print(f"\n\u001b[38;5;9merror: could not decrypt - ensure the correct gpg key is present\u001b[0m\n")
s_exit(1)
def optimized_edit(_lines, _edit_data, _edit_line): # ensures an edited entry is optimized for best compatibility
while len(_lines) < _edit_line + 1:
_lines += ['\n']
if _edit_data is not None:
_lines[_edit_line] = _edit_data.strip('\n').rstrip() + '\n'
for _num in range(len(_lines)):
if not _lines[_num].endswith('\n'):
_lines[_num] += '\n'
for _num in reversed(range(len(_lines))):
if _lines[_num] == '\n':
_lines = _lines[:-1]
elif _lines[_num].endswith('\n'):
_lines[_num] = _lines[_num].rstrip()
break
else:
break
return _lines
def edit_note(_shm_folder, _shm_entry, _lines): # edits the note attached to an entry
_reg_lines = _lines[0:3]
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'w').writelines(_lines[3:])
system(f"{editor} {tmp_dir}{_shm_folder}/{_shm_entry}-n")
_new_notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n").readlines()
while len(_reg_lines) < 3:
_reg_lines += ['\n']
_noted_lines = _reg_lines + _new_notes
return _noted_lines
def copy_name_check(_port, _username_ssh, _ip, _client_device_name):
# attempts to connect to the user's server via ssh to register the device for syncing
_command = f"ssh -o ConnectTimeout=3 -i '{path.expanduser('~/.ssh/sshyp')}' -p {_port} {_username_ssh}@{_ip} " \
f"\"touch '/home/{_username_ssh}/.config/sshyp/devices/{_client_device_name}'\""
try:
run(_command, shell=True, stderr=PIPE, check=True, close_fds=True)
except CalledProcessError:
print('\n\u001b[38;5;9mwarning: ssh connection could not be made - ensure the public key (~/.ssh/sshyp.pub) is '
'registered on the remote server and that the entered ip, port, and username are correct\n\nsyncing '
'functionality will be disabled until this is addressed\u001b[0m\n')
open(path.expanduser('~/.config/sshyp/ssh-error'), 'w').write('1')
return 1
open(path.expanduser('~/.config/sshyp/ssh-error'), 'w').write('0')
return 0
# BELOW - argument-specific functions
def tweak(): # runs configuration wizard
_divider = f"\n{'=' * (get_terminal_size()[0] - int((.5 * get_terminal_size()[0])))}\n\n"
# storage/config directory creation
Path(path.expanduser('~/.local/share/sshyp')).mkdir(0o700, parents=True, exist_ok=True)
Path(path.expanduser('~/.config/sshyp/devices')).mkdir(0o700, parents=True, exist_ok=True)
if not Path(f"{path.expanduser('~/.config/sshyp/tmp')}").exists():
if uname()[0] == 'Haiku' or uname()[0] == 'FreeBSD':
system(f"ln -s /tmp {path.expanduser('~/.config/sshyp/tmp')}")
elif Path("/data/data/com.termux").exists():
system(f"ln -s '/data/data/com.termux/files/usr/tmp' {path.expanduser('~/.config/sshyp/tmp')}")
else:
system(f"ln -s /dev/shm {path.expanduser('~/.config/sshyp/tmp')}")
# device type configuration
_device_type = input('\nclient or server installation? (C/s) ')
if _device_type.lower() == 's':
open(path.expanduser('~/.config/sshyp/sshyp-device'), 'w').write(_device_type)
Path(path.expanduser('~/.config/sshyp/deleted')).mkdir(0o700, parents=True, exist_ok=True)
print(f"\n\u001b[4;1mmake sure the ssh service is running and properly configured\u001b[0m\n"
f"{_divider}configuration complete\n")
s_exit(0)
else:
# device type configuration
_device_type = 'c' # ensure device type flag is properly set
open(path.expanduser('~/.config/sshyp/sshyp-device'), 'w').write(_device_type)
# gpg configuration
_gpg_id = input(f"{_divider}sshyp requires the use of a unique gpg key - use an (e)xisting key or (g)enerate "
f"a new one? (E/g) ")
if _gpg_id.lower() != 'g':
system(f"{gpg} -k")
_gpg_id = str(input('gpg key id: '))
else:
print('\na unique gpg key is being generated for you...')
if not Path(path.expanduser('~/.config/sshyp/gpg-gen')).is_file():
open(path.expanduser('~/.config/sshyp/gpg-gen'), 'w').writelines([
'Key-Type: 1\n', 'Key-Length: 4096\n', 'Key-Usage: sign encrypt\n', 'Name-Real: sshyp\n',
'Name-Comment: gpg-sshyp\n', 'Name-Email: https://github.com/rwinkhart/sshyp\n', 'Expire-Date: 0'])
if uname()[0] == 'Haiku':
run(gpg + ' --batch --generate-key --passphrase ' + "'" +
input('\ngpg passphrase: ') + "'" + " '" +
path.expanduser('~/.config/sshyp/gpg-gen') + "'", shell=True)
else:
run(f"{gpg} --batch --generate-key '{path.expanduser('~/.config/sshyp/gpg-gen')}'", shell=True)
remove(path.expanduser('~/.config/sshyp/gpg-gen'))
_gpg_id = run(f"{gpg} -k", shell=True, stdout=PIPE, text=True).stdout.split('\n')[-4].strip()
# lock file generation
if Path(path.expanduser('~/.config/sshyp/lock.gpg')).is_file():
remove(path.expanduser('~/.config/sshyp/lock.gpg'))
open(path.expanduser('~/.config/sshyp/lock'), 'w')
system(f"{gpg} -qr {str(_gpg_id)} -e {path.expanduser('~/.config/sshyp/lock')}")
remove(path.expanduser('~/.config/sshyp/lock'))
# ssh key configuration
_ssh_gen = (input(f"{_divider}make sure the ssh service on the remote server is running and properly "
f"configured\n\nsync support requires a unique ssh key - would you like to have this "
f"automatically generated? (Y/n) "))
if _ssh_gen.lower() != 'n':
if uname()[0] == 'Haiku':
Path(f"{path.expanduser('~')}/.ssh").mkdir(0o700, exist_ok=True)
system('ssh-keygen -t ed25519 -f ~/.ssh/sshyp')
else:
print(f"\n\u001b[4;1mensure that the key file you are using is located at "
f"{path.expanduser('~/.ssh/sshyp')}\u001b[0m")
# ssh ip+port configuration
_ip_port = str(input(f"{_divider}example input: 10.10.10.10:9999\n\nip and ssh port of the remote server: "))
_ip, _sep, _port = _ip_port.partition(':')
# ssh user configuration
_username_ssh = str(input('\nusername of the remote server: '))
# sshyp-only data storage
open(path.expanduser('~/.config/sshyp/sshyp-data'), 'w')\
.write(_gpg_id + '\n' + input(f"{_divider}example input: vim\n\npreferred text editor: "))
# sshync profile generation
sshync.make_profile(path.expanduser('~/.config/sshyp/sshyp.sshync'),
path.expanduser('~/.local/share/sshyp/'), f"/home/{_username_ssh}/.local/share/sshyp/",
path.expanduser('~/.ssh/sshyp'), _ip, _port, _username_ssh)
# device name configuration
for _name in listdir(path.expanduser('~/.config/sshyp/devices')): # remove existing device name
remove(f"{path.expanduser('~/.config/sshyp/devices/')}{_name}")
print(f"{_divider}\u001b[4;1mimportant:\u001b[0m this name \u001b[4;1mmust\u001b[0m be unique amongst your "
f"client devices\n\nthis is used to keep track of which devices have up-to-date databases\n")
_client_device_name = str(input('device name: '))
open(f"{path.expanduser('~/.config/sshyp/devices/')}{_client_device_name}", 'w')
copy_name_check(_port, _username_ssh, _ip, _client_device_name)
print(f"{_divider}configuration complete\n")
def print_info(): # prints help text based on argument
if argument_list[1] == 'help' or argument_list[1] == '--help' or argument_list[1] == '-h':
print('\n\u001b[1msshyp copyright (c) 2021-2022 randall winkhart\u001b[0m\n')
print("this is free software, and you are welcome to redistribute it under certain conditions;\nthis program "
"comes with absolutely no warranty;\ntype `sshyp license' for details")
print('\n\u001b[1musage:\u001b[0m sshyp [option [flag] [<entry name>]] | [/<entry name>]\n')
print('\u001b[1moptions:\u001b[0m')
print('help/--help/-h bring up this menu')
print('version/-v display sshyp version info')
print('tweak configure sshyp')
print('add add an entry')
print('gen generate a new password')
print('edit edit an existing entry')
print('copy copy details of an entry to your clipboard')
print('shear/-rm delete an existing entry')
print('sync/-s manually sync the entry directory via sshync\n')
print('\u001b[1mflags:\u001b[0m')
print('add:')
print(' password/-p add a password entry')
print(' note/-n add a note entry')
print(' folder/-f add a new folder for entries')
print('edit:')
print(' rename/relocate/-r rename or relocate an entry')
print(' username/-u change the username of an entry')
print(' password/-p change the password of an entry')
print(' note/-n change the note attached to an entry')
print(' url/-l change the url attached to an entry')
print('copy:')
print(' username/-u copy the username of an entry to your clipboard')
print(' password/-p copy the password of an entry to your clipboard')
print(' url/-l copy the url of an entry to your clipboard')
print(' note/-n copy the note of an entry to your clipboard')
print('gen:')
print(' update/-u generate a password for an existing entry\n')
print("\u001b[1mtip:\u001b[0m you can quickly read an entry with 'sshyp /<entry name>'")
elif argument_list[1] == 'version' or argument_list[1] == '-v':
print('\nsshyp is a simple, self-hosted, sftp-synchronized password manager\nfor unix(-like) systems (haiku/'
'freebsd/linux/termux)\n\nsshyp is a viable alternative to (and compatible with) pass/password-store\n')
print(" .. \u001b[38;5;9m♥♥ ♥♥\u001b[0m ..\n .''.''/()\\ \u001b[38;5;13m"
"♥♥♥♥♥♥♥\u001b[0m /()\\''.''.\n * : \u001b[38;5;9m♥♥♥♥♥\u001b[0m : *"
"\n `..'..' \u001b[38;5;13m♥♥♥\u001b[0m `..'..'\n // \\\\ "
"\u001b[38;5;9m♥\u001b[0m // \\\\")
print('\u001b[38;5;7;48;5;8m<><><><><><><><><><><><><><><><><><><><><><><><><><><><>\u001b[0m')
print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m '
'\u001b[38;5;7;48;5;8m/\u001b[0m')
print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m \u001b[38;5;15;48;5;8msshyp copyright (c) 2021-2022 '
'randall winkhart\u001b[38;5;15;48;5;15m \u001b[38;5;7;48;5;8m/\u001b[0m')
print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m '
'\u001b[38;5;7;48;5;8m/\u001b[0m')
print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m \u001b[38;5;15;48;5;8mversion 1.1.2'
'\u001b[38;5;15;48;5;15m \u001b[38;5;7;48;5;8m/\u001b[0m')
print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m \u001b[38;5;15;48;5;8mthe sheecrets '
'update\u001b[38;5;15;48;5;15m \u001b[38;5;7;48;5;8m/\u001b[0m')
print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m '
'\u001b[38;5;7;48;5;8m/\u001b[0m')
print('\u001b[38;5;7;48;5;8m<><><><><><><><><><><><><><><><><><><><><><><><><><><><>\u001b[0m\n')
print('see https://github.com/rwinkhart/sshyp for more information\n')
elif argument_list[1] == 'license':
print('\nThis program is free software: you can redistribute it and/or modify it under the terms of the GNU '
'General\nPublic License as published by the Free Software Foundation, either version 3 of the License,'
'\nor (at your option) any later version.\n\nThis program is distributed in the hope that it will be '
'useful, but WITHOUT ANY WARRANTY;\nwithout even the implied warranty of MERCHANTABILITY or FITNESS FOR A'
' PARTICULAR PURPOSE.\nSee the GNU General Public License for more details.'
'\n\nhttps://opensource.org/licenses/GPL-3.0\n')
elif argument_list[1] == 'add':
print('\n\u001b[1musage:\u001b[0m sshyp add [flag [<entry name>]]\u001b[0m\n')
print('\u001b[1mflags:\u001b[0m')
print('add:')
print(' password/-p add a password entry')
print(' note/-n add a note entry')
print(' folder/-f add a new folder for entries\n')
elif argument_list[1] == 'edit':
print('\n\u001b[1musage:\u001b[0m sshyp edit [flag [<entry name>]]\u001b[0m\n')
print('\u001b[1mflags:\u001b[0m')
print('edit:')
print(' rename/relocate/-r rename or relocate an entry')
print(' username/-u change the username of an entry')
print(' password/-p change the password of an entry')
print(' url/-l change the url attached to an entry')
print(' note/-n change the note attached to an entry\n')
elif argument_list[1] == 'copy':
print('\n\u001b[1musage:\u001b[0m sshyp copy [flag [<entry name>]]\u001b[0m\n')
print('\u001b[1mflags:\u001b[0m')
print('copy:')
print(' username/-u copy the username of an entry to your clipboard')
print(' password/-p copy the password of an entry to your clipboard')
print(' url/-l copy the url of an entry to your clipboard')
print(' note/-n copy the note of an entry to your clipboard\n')
def no_arg(): # displays a list of entries and gives an option to select one for viewing
print("\nfor a list of usable commands, run 'sshyp help'")
_entry_name = entry_name_fetch('entry to read: ')
if not Path(f"{directory}{_entry_name}.gpg").exists():
print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) does not exist\u001b[0m\n")
s_exit(1)
_shm_folder, _shm_entry = shm_gen()
decrypt(directory + _entry_name, _shm_folder, _shm_entry, gpg)
entry_reader(f"{tmp_dir}{_shm_folder}/{_shm_entry}")
rmtree(f"{tmp_dir}{_shm_folder}")
def read_shortcut(): # shortcut to quickly read an entry
if not Path(f"{directory}{argument.replace('/', '', 1)}.gpg").exists():
print(f"\n\u001b[38;5;9merror: entry ({argument.replace('/', '', 1)}) does not exist\u001b[0m\n")
s_exit(1)
_shm_folder, _shm_entry = shm_gen()
decrypt(directory + argument.replace('/', '', 1), _shm_folder, _shm_entry, gpg)
entry_reader(f"{tmp_dir}{_shm_folder}/{_shm_entry}")
rmtree(f"{tmp_dir}{_shm_folder}")
def sync(): # calls sshync to sync changes to the user's server
print('\nsyncing entries with the server device...\n')
# check for deletions
system(f"ssh -i '{path.expanduser('~/.ssh/sshyp')}' -p {port} {username_ssh}@{ip} \"cd /bin; python -c "
f"'import sshypRemote; sshypRemote.deletion_check(\"'\"{client_device_name}\"'\")'\"")
system(f"scp -pqs -P {port} -i '{path.expanduser('~/.ssh/sshyp')}' {username_ssh}@{ip}:'/home/{username_ssh}"
f"/.config/sshyp/deletion_database' {path.expanduser('~/.config/sshyp/')}")
try:
_deletion_database = open(path.expanduser('~/.config/sshyp/deletion_database')).readlines()
except (FileNotFoundError, IndexError):
print('\n\u001b[38;5;9merror: the deletion database does not exist or is corrupted\u001b[0m\n')
_deletion_database = None
s_exit(1)
for _file in _deletion_database:
try:
if silent_sync != 1:
print(f"\u001b[38;5;208m{_file[:-1]}\u001b[0m has been sheared, removing...")
if _file[:-1].endswith('/'):
rmtree(f"{directory}{_file[:-1]}")
else:
remove(f"{directory}{_file[:-1]}.gpg")
except FileNotFoundError:
if silent_sync != 1:
print('location does not exist locally')
# check for new folders
system(f"ssh -i '{path.expanduser('~/.ssh/sshyp')}' -p {port} {username_ssh}@{ip} \"cd /bin; python -c "
f"'import sshypRemote; sshypRemote.folder_check()'\"")
system(f"scp -pqs -P {port} -i '{path.expanduser('~/.ssh/sshyp')}' {username_ssh}@{ip}:'/home/{username_ssh}"
f"/.config/sshyp/folder_database' {path.expanduser('~/.config/sshyp/')}")
try:
_folder_database = open(path.expanduser('~/.config/sshyp/folder_database')).readlines()
except (FileNotFoundError, IndexError):
print('\n\u001b[38;5;9merror: the folder database does not exist or is corrupted\u001b[0m\n')
_folder_database = None
s_exit(1)
for _folder in _folder_database:
if Path(f"{path.expanduser('~')}{_folder[:-1]}").is_dir():
pass
else:
print(f"\u001b[38;5;2m{_folder.replace('/.local/share/sshyp/', '')[:-1]}/\u001b[0m does not exist locally, "
f"creating...")
Path(f"{path.expanduser('~')}{_folder[:-1]}").mkdir(0o700, parents=True, exist_ok=True)
# set permissions before uploading
system('find ' + directory + ' -type d -exec chmod -R 700 {} +')
system('find ' + directory + ' -type f -exec chmod -R 600 {} +')
sshync.run_profile(path.expanduser('~/.config/sshyp/sshyp.sshync'))
def add_entry(): # adds a new entry
_shm_folder, _shm_entry = None, None # sets base-line values to avoid errors
if len(argument_list) < 4:
_entry_name = entry_name_fetch('name of new entry: ')
else:
_entry_name = entry_name_fetch(2)
if Path(f"{directory}{_entry_name}.gpg").is_file():
print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) already exists\u001b[0m\n")
s_exit(1)
if argument_list[2] == 'note' or argument_list[2] == '-n':
_shm_folder, _shm_entry = shm_gen()
system(f"{editor} {tmp_dir}{_shm_folder}/{_shm_entry}-n")
_notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'r').read()
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w').writelines(optimized_edit(['', '', '', _notes], None, -1))
elif argument_list[2] == 'password' or argument_list[2] == '-p':
_username = str(input('username: '))
_password = str(input('password: '))
_url = str(input('url: '))
_add_note = input('add a note to this entry? (y/N) ')
_shm_folder, _shm_entry = shm_gen()
if _add_note.lower() == 'y':
system(f"{editor} {tmp_dir}{_shm_folder}/{_shm_entry}-n")
_notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'r').read()
else:
_notes = ''
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w')\
.writelines(optimized_edit([_password, _username, _url, _notes], None, -1))
print('\n\u001b[1mentry preview:\u001b[0m')
entry_reader(f"{tmp_dir}{_shm_folder}/{_shm_entry}")
encrypt(directory + _entry_name, _shm_folder, _shm_entry, gpg, gpg_id)
def add_folder(): # creates a new folder
if len(argument_list) < 4:
_entry_name = entry_name_fetch('name of new folder: ')
else:
_entry_name = entry_name_fetch(2)
Path(directory + _entry_name).mkdir(0o700)
system(f"ssh -i '{path.expanduser('~/.ssh/sshyp')}' -p {port} {username_ssh}@{ip} \"mkdir -p '{directory_ssh}"
f"{_entry_name}'\"")
def rename(): # renames an entry or folder
if argument == 'edit rename' or argument == 'edit relocate' or argument == 'edit -r':
_entry_name = entry_name_fetch('entry/folder to rename/relocate: ')
else:
_entry_name = entry_name_fetch(2)
if not Path(f"{directory}{_entry_name}.gpg").is_file() and not Path(f"{directory}{_entry_name}").is_dir():
print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) does not exist\u001b[0m\n")
s_exit(1)
_new_name = entry_name_fetch('new name: ')
if Path(f"{directory}{_new_name}.gpg").is_file() or Path(f"{directory}{_new_name}").is_dir():
print(f"\n\u001b[38;5;9merror: ({_new_name}) already exists\u001b[0m\n")
s_exit(1)
if _entry_name.endswith('/'):
move(f"{directory}{_entry_name}", f"{directory}{_new_name}")
system(f"ssh -i '{path.expanduser('~/.ssh/sshyp')}' -p {port} {username_ssh}@{ip} \"mkdir -p '{directory_ssh}"
f"{_new_name}'\"")
else:
move(f"{directory}{_entry_name}.gpg", f"{directory}{_new_name}.gpg")
system(f"ssh -i '{path.expanduser('~/.ssh/sshyp')}' -p {port} {username_ssh}@{ip} \"cd /bin; python -c "
f"'import sshypRemote; sshypRemote.delete(\"'\"{_entry_name}\"'\")'\"")
def edit(): # edits the contents of an entry
_shm_folder, _shm_entry, _detail, _edit_line = None, None, None, None # sets values to avoid PEP8 warnings
if len(argument_list) < 4:
_entry_name = entry_name_fetch('entry to edit: ')
else:
_entry_name = entry_name_fetch(2)
if not Path(f"{directory}{_entry_name}.gpg").is_file():
print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) does not exist\u001b[0m\n")
s_exit(1)
_shm_folder, _shm_entry = shm_gen()
decrypt(directory + _entry_name, _shm_folder, _shm_entry, gpg)
if argument_list[2] == 'username' or argument_list[2] == '-u':
_detail, _edit_line = str(input('username: ')), 1
elif argument_list[2] == 'password' or argument_list[2] == '-p':
_detail, _edit_line = str(input('password: ')), 0
elif argument_list[2] == 'url' or argument_list[2] == '-l':
_detail, _edit_line = str(input('url: ')), 2
if argument_list[2] == 'note' or argument_list[2] == '-n':
_edit_line = 2
_new_lines = optimized_edit(edit_note(_shm_folder, _shm_entry,
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'r').readlines()), None, -1)
else:
_new_lines = optimized_edit(open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'r').readlines(), _detail, _edit_line)
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w').writelines(_new_lines)
remove(f"{directory}{_entry_name}.gpg")
print('\n\u001b[1mentry preview:\u001b[0m')
entry_reader(f"{tmp_dir}{_shm_folder}/{_shm_entry}")
encrypt(directory + _entry_name, _shm_folder, _shm_entry, gpg, gpg_id)
def gen(): # generates a password for a new or an existing entry
_username, _url, _notes = None, None, None # sets base-line values to avoid errors
if argument == 'gen update' or argument == 'gen -u' or argument == 'gen':
_entry_name = entry_name_fetch('name of entry: ')
elif argument_list[2] == 'update' or argument_list[2] == '-u':
_entry_name = entry_name_fetch(2)
if not Path(f"{directory}{_entry_name}.gpg").is_file():
print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) does not exist\u001b[0m\n")
s_exit(1)
else:
_entry_name = entry_name_fetch(1)
if len(argument_list) == 2 or (not argument_list[2] == 'update' and not argument_list[2] == '-u'):
if Path(f"{directory}{_entry_name}.gpg").is_file():
print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) already exists\u001b[0m\n")
s_exit(1)
_username = str(input('username: '))
_password = pass_gen()
_url = str(input('url: '))
_add_note = input('add a note to this entry? (y/N) ')
_shm_folder, _shm_entry = shm_gen()
if _add_note.lower() == 'y':
system(f"{editor} {tmp_dir}{_shm_folder}/{_shm_entry}-n")
_notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'r').read()
else:
_notes = ''
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w')\
.writelines(optimized_edit([_password, _username, _url, _notes], None, -1))
else:
_shm_folder, _shm_entry = shm_gen()
decrypt(directory + _entry_name, _shm_folder, _shm_entry, gpg)
_new_lines = optimized_edit(open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'r').readlines(), pass_gen(), 0)
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w').writelines(_new_lines)
remove(f"{directory}{_entry_name}.gpg")
print('\n\u001b[1mentry preview:\u001b[0m')
entry_reader(f"{tmp_dir}{_shm_folder}/{_shm_entry}")
encrypt(directory + _entry_name, _shm_folder, _shm_entry, gpg, gpg_id)
def copy_data(): # copies a specified field of an entry to the clipboard
if len(argument_list) < 4:
_entry_name = entry_name_fetch('entry to copy: ')
else:
_entry_name = entry_name_fetch(2)
if not Path(f"{directory}{_entry_name}.gpg").is_file():
print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) does not exist\u001b[0m\n")
s_exit(1)
_shm_folder, _shm_entry = shm_gen()
decrypt(directory + _entry_name, _shm_folder, _shm_entry, gpg)
_copy_line = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'r').readlines()
if uname()[0] == 'Haiku': # Haiku clipboard detection
if argument_list[2] == 'username' or argument_list[2] == '-u':
system('clipboard -c ' + "'" + _copy_line[1].replace('\n', '').replace("'", "'\\''") + "'")
elif argument_list[2] == 'password' or argument_list[2] == '-p':
system('clipboard -c ' + "'" + _copy_line[0].replace('\n', '').replace("'", "'\\''") + "'")
elif argument_list[2] == 'url' or argument_list[2] == '-l':
system('clipboard -c ' + "'" + _copy_line[2].replace('\n', '').replace("'", "'\\''") + "'")
elif argument_list[2] == 'note' or argument_list[2] == '-n':
system('clipboard -c ' + "'" + _copy_line[3].replace('\n', '').replace("'", "'\\''") + "'")
Popen('sleep 30; clipboard -r', shell=True, close_fds=True)
elif Path("/data/data/com.termux").exists(): # Termux (Android) clipboard detection
if argument_list[2] == 'username' or argument_list[2] == '-u':
system('termux-clipboard-set ' + "'" + _copy_line[1].replace('\n', '').replace("'", "'\\''") + "'")
elif argument_list[2] == 'password' or argument_list[2] == '-p':
system('termux-clipboard-set ' + "'" + _copy_line[0].replace('\n', '').replace("'", "'\\''") + "'")
elif argument_list[2] == 'url' or argument_list[2] == '-l':
system('termux-clipboard-set ' + "'" + _copy_line[2].replace('\n', '').replace("'", "'\\''") + "'")
elif argument_list[2] == 'note' or argument_list[2] == '-n':
system('termux-clipboard-set ' + "'" + _copy_line[3].replace('\n', '').replace("'", "'\\''") + "'")
Popen("sleep 30; termux-clipboard-set ''", shell=True, close_fds=True)
elif environ.get('WAYLAND_DISPLAY') == 'wayland-0': # Wayland clipboard detection
if argument_list[2] == 'username' or argument_list[2] == '-u':
system('wl-copy ' + "'" + _copy_line[1].replace('\n', '').replace("'", "'\\''") + "'")
elif argument_list[2] == 'password' or argument_list[2] == '-p':
system('wl-copy ' + "'" + _copy_line[0].replace('\n', '').replace("'", "'\\''") + "'")
elif argument_list[2] == 'url' or argument_list[2] == '-l':
system('wl-copy ' + "'" + _copy_line[2].replace('\n', '').replace("'", "'\\''") + "'")
elif argument_list[2] == 'note' or argument_list[2] == '-n':
system('wl-copy ' + "'" + _copy_line[3].replace('\n', '').replace("'", "'\\''") + "'")
Popen('sleep 30; wl-copy -c', shell=True, close_fds=True)
else: # X11 clipboard detection
if argument_list[2] == 'username' or argument_list[2] == '-u':
system('echo -n ' + "'" + _copy_line[1].replace('\n', '').replace("'", "'\\''") + "'" + ' | xclip -sel c')
elif argument_list[2] == 'password' or argument_list[2] == '-p':
system('echo -n ' + "'" + _copy_line[0].replace('\n', '').replace("'", "'\\''") + "'" + ' | xclip -sel c')
elif argument_list[2] == 'url' or argument_list[2] == '-l':
system('echo -n ' + "'" + _copy_line[2].replace('\n', '').replace("'", "'\\''") + "'" + ' | xclip -sel c')
elif argument_list[2] == 'note' or argument_list[2] == '-n':
system('echo -n ' + "'" + _copy_line[3].replace('\n', '').replace("'", "'\\''") + "'" + ' | xclip -sel c')
Popen("sleep 30; echo -n '' | xclip -sel c", shell=True, close_fds=True)
rmtree(f"{tmp_dir}{_shm_folder}")
def remove_data(): # deletes an entry from the server and flags it for local deletion on sync
if argument == 'shear' or argument == '-rm':
_entry_name = entry_name_fetch('entry/folder to shear: ')
else:
_entry_name = entry_name_fetch(1)
decrypt(path.expanduser('~/.config/sshyp/lock.gpg'), 0, 0, gpg)
system(f"ssh -i '{path.expanduser('~/.ssh/sshyp')}' -p {port} {username_ssh}@{ip} \"cd /bin; python -c "
f"'import sshypRemote; sshypRemote.delete(\"'\"{_entry_name}\"'\")'\"")
if __name__ == "__main__":
try:
silent_sync, ssh_error = 0, 0
# retrieve typed argument
argument_list = argv
argument = ' '.join(argument_list[1:])
if uname()[0] == 'Haiku': # set proper gpg command for OS
gpg = 'gpg --pinentry-mode loopback'
else:
gpg = 'gpg'
# import saved userdata
if argument != 'tweak':
device_type = ''
tmp_dir = path.expanduser('~/.config/sshyp/tmp/')
try:
device_type = open(path.expanduser('~/.config/sshyp/sshyp-device')).read().strip()
if device_type == 'c':
ssh_info = sshync.get_profile(path.expanduser('~/.config/sshyp/sshyp.sshync'))
username_ssh = ssh_info[0].replace('\n', '')
ip = ssh_info[1].replace('\n', '')
port = ssh_info[2].replace('\n', '')
directory = str(ssh_info[3].replace('\n', ''))
directory_ssh = str(ssh_info[4].replace('\n', ''))
client_device_name = listdir(path.expanduser('~/.config/sshyp/devices'))[0]
sshyp_data = open(path.expanduser('~/.config/sshyp/sshyp-data')).readlines()
gpg_id = sshyp_data[0].replace('\n', '')
editor = sshyp_data[1].replace('\n', '')
ssh_error = int(open(path.expanduser('~/.config/sshyp/ssh-error')).read().strip())
if ssh_error != 0:
ssh_error = copy_name_check(port, username_ssh, ip, client_device_name)
else:
print('running as server, option disabled')
s_exit(0)
except (FileNotFoundError, IndexError):
if device_type.lower() != 's':
print('\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')
print("not all necessary configuration files are present - please run 'sshyp tweak'")
print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n')
s_exit(1)
else:
tweak()
s_exit(0)
# run function based on arguments
if argument.startswith('/'):
read_shortcut()
elif argument == '':
no_arg()
elif argument == 'help' or argument == '--help' or argument == '-h' or argument == 'license' or argument \
== 'version' or argument == '-v':
print_info()
elif argument_list[1] == 'add':
if len(argument_list) == 2:
print_info()
elif argument_list[2] == 'note' or argument_list[2] == '-n' or argument_list[2] == 'password' or \
argument_list[2] == '-p':
add_entry()
elif argument_list[2] == 'folder' or argument_list[2] == '-f':
add_folder()
else:
print_info()
s_exit(0)
elif argument_list[1] == 'edit':
if len(argument_list) == 2:
print_info()
elif argument_list[2] == 'rename' or argument_list[2] == 'relocate' or argument_list[2] == '-r':
silent_sync = 1
rename()
elif argument_list[2] == 'username' or argument_list[2] == '-u' or argument_list[2] == 'password' or \
argument_list[2] == '-p' or argument_list[2] == 'url' or argument_list[2] == '-l' or \
argument_list[2] == 'note' or argument_list[2] == '-n':
edit()
else:
print_info()
s_exit(0)
elif argument_list[1] == 'gen':
gen()
elif argument_list[1] == 'copy':
if len(argument_list) == 2:
print_info()
elif argument_list[2] == 'username' or argument_list[2] == '-u' or argument_list[2] == 'password' or \
argument_list[2] == '-p' or argument_list[2] == 'url' or argument_list[2] == '-l' or \
argument_list[2] == 'note' or argument_list[2] == '-n':
try:
copy_data()
except IndexError:
print(f"\n\u001b[38;5;9merror: field does not exist in entry\u001b[0m\n")
s_exit(1)
else:
print_info()
elif argument_list[1] == 'shear' or argument_list[1] == '-rm':
remove_data()
elif argument_list[1] != 'sync' and argument_list[1] != '-s':
print(f"\n\u001b[38;5;9merror: invalid argument - run 'sshyp help' to list usable commands\u001b[0m\n")
s_exit(1)
# sync if any changes were made
if len(argument_list) > 1 and ssh_error == 0 and \
((argument_list[1] == 'sync' or argument_list[1] == '-s' or argument_list[1] == 'gen' or
argument_list[1] == 'shear' or argument_list[1] == '-rm') or ((argument_list[1] == 'add'
or argument_list[1] == 'edit')
and len(argument_list) > 2)):
sync()
s_exit(0)
except KeyboardInterrupt:
print('\n')
s_exit(0)
-42
View File
@@ -1,42 +0,0 @@
#!/usr/bin/env python3
# external modules
from os import listdir, remove, walk
from os.path import expanduser
from shutil import rmtree
# utility functions
def delete(_file_path): # deletes an entry or folder, should be run remotely via ssh
try:
if _file_path.endswith('/'):
rmtree(f"{expanduser('~/.local/share/sshyp/')}{_file_path}")
else:
remove(f"{expanduser('~/.local/share/sshyp/')}{_file_path}.gpg")
except FileNotFoundError:
print('location does not exist remotely')
for _device_name in listdir(expanduser('~/.config/sshyp/devices')):
open(f"{expanduser('~/.config/sshyp/deleted/')}{_file_path.replace('/', '@')}^&*{_device_name}", 'w')
def deletion_check(_client_device_name): # creates a list of entries and folders to be deleted from a local machine
open(expanduser('~/.config/sshyp/deletion_database'), 'w').write('')
for _file in listdir(expanduser('~/.config/sshyp/deleted')):
_file_path = _file.replace('@', '/').split('^&*')[0]
_device = _file.split('^&*')[1]
if _device == _client_device_name:
try:
remove(f"{expanduser('~/.config/sshyp/deleted/')}{_file}")
except FileNotFoundError:
pass
open(expanduser('~/.config/sshyp/deletion_database'), 'a').write(_file_path + '\n')
def folder_check(): # creates a list of folders to be compared with those of a local machine
open(expanduser('~/.config/sshyp/folder_database'), 'w').write('')
for _root, _directories, _files in walk(expanduser('~/.local/share/sshyp')):
for _dir in _directories:
open(expanduser('~/.config/sshyp/folder_database'), 'a')\
.write(f"{_root.replace(expanduser('~'), '')}/{_dir}\n")
Executable → Regular
+103
View File
@@ -1,3 +1,106 @@
sshyp v1.3.0
the serious shepherd update
this release ties up many of sshyp's loose ends where there was
room for major performance, compatibility, and security improvements
compatibility-breaking changes:
- due to a near full re-write of the syncing functionality, all clients and servers
must be updated to this release (v1.3.0 is not backwards compatible with any prior release)
- it is recommended to either delete the contents of ~/.config/sshyp/deleted (on the server-side)
or sync all of your clients before updating
^ old entries in this folder will throw errors with v1.3.0
user-facing features:
- none - all changes were under-the-hood - the user experience should be
exactly the same as v1.2.0 - just faster, less buggy, and more secure
major fixes/optimizations:
- a near full re-write of the syncing functionality
^ all syncing logic has been moved into sshync.py (from sshyp.py and sshypRemote.py)
^ in my setup, a dry, local "sshyp sync" went from 2.00+ seconds (v1.2.0) to 0.36 seconds (v1.3.0)
^ the performance improvements are even greater when syncing from outside your local network
- the following character sequences will no longer break the syncing logic: "@", "^&*", and "*&^"
^ ASCII separator characters 29-31 are now used, instead
- os.system has been replaced with subprocess.run in all cases, shell=True is no longer used with subprocess.run
^ this protects against shell escape attacks and potentially makes sshyp more compatible with some environments
- replaced shell commands with python built-in library functions where applicable
^ this brings speed and compatibility improvements
- sshyp should no longer incorrectly assume an X11 environment when Wayland is in use
^ this fixes clipboard support in some Wayland environments, such as Sway (Plasma/Gnome/Phosh were unaffected)
- sshyp now uses the default pinentry on Haiku thanks to haikuports/haikuports#7457
^ this brings the Haiku port in-line with the other sshyp packages in terms of security
- "python3" is now called over ssh, rather than "python"
^ some environments do not have a "python" symlink, or it links to "python2" - changing this increases compatibility
- fixed an issue from v1.2.0 where renaming threw an error if not in offline mode
other notable changes:
- quick-unlock password input is now hidden while the user is typing
^ user input is now invisible to prevent snooping
- lots of smaller optimizations not listed here
<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
sshyp v1.2.0
the brisk bahh update
this release focuses on speeding up the sshyp user experience by adding
new features that reduce wasted time
compatibility-breaking changes:
- new configuration options (quick-unlock, offline mode) have been added and
the configuration files have been reorganized
^ simply running "sshyp tweak" and following the setup wizard will correct any compatibility
issues
- for quick-unlock security, device names have been replaced with more secure device ids
^ older device names are still compatible, but for security reasons it is recommended
to delete any pre-existing device names from the server and allow "sshyp tweak" to re-register
your devices
user-facing features:
- quick-unlock mode has been added
^ this allows you to use a shortened version of your password by verifying that your device
is whitelisted on your sshyp server - it's both faster and more secure than standard unlock,
but it requires an active connection to your sshyp server to authenticate (otherwise it will
fall back to standard unlock)
- full support for offline usage
^ though sshyp could be used without a server before, it now can be configured to not attempt
to find one ever - this saves time and hides sync failure error messages
- bash completions have been added
^ if you have bash-completion installed, you can now use the tab key in bash to auto-complete
sshyp arguments and entry names (client only, not added for server-specific arguments)
^ if you do not have bash-completion installed, you can source
/usr/share/bash-completion/completions/sshyp (Linux/BSD) or
/boot/system/data/bash-completion/completions/sshyp (Haiku) in your ~/.bashrc to
use this feature
fixes/optimizations:
- fixed entries with multi-word titles failing to decrypt
- password generation is now much faster and more resource efficient
- there is no longer a length limit on generated passwords
- improved visual consistency of help menus
- rarely used modules are now imported only when needed
- sshyp now uses one fewer configuration file
other notable changes:
- sshyp is now specifically licensed under the GPL-3.0-only (keyword: only)
- sshyp now has some possible arguments and its own help menu when running in server mode
- temporary files in /dev/shm are now generated with more complex names
- sshyp now installs in /usr/lib/sshyp (Linux/BSD) or /system/lib/sshyp (Haiku) instead of
/usr/bin or /bin (it is still symlinked to the old directories)
<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
sshyp v1.1.2
the sheecrets update - patch two
Executable → Regular
+63 -26
View File
@@ -1,11 +1,13 @@
.TH sshyp 1 "27 August 2022" "v1.1.2" "sshyp man page"
.TH sshyp 1 "14 December 2022" "v1.3.0" "sshyp man page"
.SH NAME
sshyp \- A very simple self-hosted, synchronized password manager for UNIX(-like) systems. Alternative to (and compatible with) pass/password-store.
.SH SYNOPSIS
sshyp [OPTION] [[FLAG]] [/<entry name>]
Client Usage: sshyp [option [flag] [<entry name>]] | [/<entry name>]
Server Usage: sshyp [option [flag] [<device id>]]
.SH DESCRIPTION
sshyp is a lightweight password and note management program written in Python. sshyp is used via CLI and is self-hosted with a client-server model. sshyp expects that you have access to a personal server with a properly configured ssh server, ideally accepting remote connections.
.SH EXAMPLES
.SH EXAMPLES (CLIENT)
Setting up sshyp for the first time or altering its configuration (also recommended after major updates):
sshyp tweak
@@ -32,10 +34,19 @@ Manually syncing entries with the server:
Removing an existing folder saved as ~/.local/share/sshyp/social:
sshyp shear social/
.SH EXAMPLES (SERVER)
Setting up the quick-unlock whitelist:
sshyp whitelist setup
.SH OPTIONS
USAGE: sshyp [OPTION [FLAG] [<entry name>]] | [/<entry name>]
Checking the quick-unlock whitelist status of all registered client devices:
sshyp whitelist list
Adding a device with the id "laptop-im|lAoa;&;r&o:ez((g/Dz;EVb.nO_" to the quick-unlock whitelist:
sshyp whitelist add 'laptop-im|lAoa;&;r&o:ez((g/Dz;EVb.nO_'
Removing a device with the id "laptop-im|lAoa;&;r&o:ez((g/Dz;EVb.nO_" from the quick-unlock whitelist:
sshyp whitelist del 'laptop-im|lAoa;&;r&o:ez((g/Dz;EVb.nO_'
.SH OPTIONS (CLIENT)
help/--help/-h bring up the help menu
version/-v display sshyp version info
tweak configure sshyp
@@ -45,38 +56,49 @@ USAGE: sshyp [OPTION [FLAG] [<entry name>]] | [/<entry name>]
copy copy details of an entry to your clipboard
shear/-rm delete an existing entry
sync/-s manually sync the entry directory via sshync
.SH FLAGS
.SH FLAGS (CLIENT)
add:
password/-p add a password entry
note/-n add a note entry
folder/-f add a new folder for entries
edit:
rename/relocate/-r rename or relocate an entry
username/-u change the username of an entry
password/-p change the password of an entry
note/-n change the note attached to an entry
url/-l change the url attached to an entry
copy:
username/-u copy the username of an entry to your clipboard
password/-p copy the password of an entry to your clipboard
url/-l copy the URL of an entry to your clipboard
note/-n copy the note of an entry to your clipboard
gen:
update/-u generate a password for an existing entry
.SH LIMITATIONS
The following characters/character sequences are not supported in entry/folder titles:
password/-p add a password entry
note/-n add a note entry
folder/-f add a new folder for entries
@ ^&* *&^
edit:
rename/relocate/-r rename or relocate an entry
username/-u change the username of an entry
password/-p change the password of an entry
note/-n change the note attached to an entry
url/-l change the url attached to an entry
copy:
username/-u copy the username of an entry to your clipboard
password/-p copy the password of an entry to your clipboard
url/-l copy the URL of an entry to your clipboard
note/-n copy the note of an entry to your clipboard
gen:
update/-u generate a password for an existing entry
.SH OPTIONS (SERVER)
help/--help/-h bring up this menu
version/-v display sshyp version info
tweak configure sshyp
whitelist manage the quick-unlock whitelist
.SH FLAGS (SERVER)
whitelist:
setup set up the quick-unlock whitelist
list/-l view all registered device ids and their quick-unlock whitelist status
add whitelist a device id for quick-unlock
delete/del remove a device id from the quick-unlock whitelist
.SH SETUP
sshyp operates on a client-server model, and thus requires you to have access to your own server (whether it be a physical home server or a cloud rental) with remote access via SSH.
The sshyp package includes modes for operating on both client and server devices, so only one package is necessary.
Server setup:
Configure an OpenSSH server (if sshyp was installed from the Debian package, the ssh server was not installed as a dependency and needs to be installed manually)
Configure an OpenSSH server (if sshyp was installed from the Debian package, openssh-server and openssh-sftp-server must be installed manually - if sshyp was installed from the Fedora package, openssh-server must be installed manually)
Allow remote access to the SSH server w/public key authentication (disabling password-only authentication recommended)
Install sshyp
Run "sshyp tweak" and set the device type as server
Optionally, run "sshyp whitelist setup" and configure quick-unlock
Done!
Client setup:
@@ -84,8 +106,23 @@ Client setup:
Run "sshyp tweak" and follow the prompts
Copy the generated public SSH key (located at ~/.ssh/sshyp.pub) to the server using preferred method (manually adding to authorized_keys, ssh-copy-id, etc.)
If you already have entries on the server, sync them using "sshyp sync"
Optionally, Bash completions can be enabled by installing your distribution's "bash-completion" package and restarting your terminal
Done!
Please note that the server setup intentionally does not allow the reading of entries (it does not allow adding a gpg decryption key). For security purposes, only clients can read entries.
.SH EXIT CODES
0 - no error
1 - argument error
2 - configuration error
3 - data not found error
4 - data already exists error
5 - decryption/encryption error
6 - server connection error
.SH AUTHOR
Randall Winkhart (https://github.com/rwinkhart)
+50
View File
@@ -0,0 +1,50 @@
#!/usr/bin/env bash
_path_gen() {
local sshyp_path="$HOME/.local/share/sshyp"
local sshyp_path_length="$(("${#sshyp_path}" + 1))"
readarray -t full_paths < <(find "$sshyp_path" -type f -exec ls {} \;)
for path in "${full_paths[@]}"; do
trimmed_path=$(echo ${path%????} | cut -c"$sshyp_path_length"-)
trimmed_path=$(echo ${trimmed_path// /\\ })
trimmed_paths+=("$trimmed_path")
done
} &&
# from this point, this completions script was partially generated by
# completely (https://github.com/dannyben/completely)
_sshyp_completions() {
if [ "${#COMP_WORDS[@]}" -gt "4" ]; then
return
fi
local cur=${COMP_WORDS[COMP_CWORD]}
local compline="${COMP_WORDS[@]:1:$COMP_CWORD-1}"
case "$compline" in
'add password'* | 'add -p'* | 'add note'* | 'add -n'* | 'add folder'*| 'add -f'* | 'edit relocate'* | 'edit -r'* | 'edit username'* | 'edit -u'* | 'edit password'* | 'edit -p'* | 'edit url'* | 'edit -l'* | 'edit note'* | 'edit -n'* | 'copy username'* | 'copy -u'* | 'copy password'* | 'copy -p'* | 'copy note'* | 'copy -n'* | 'copy url'* | 'copy -l'* | 'gen update'* | 'gen -u'*)
while read; do COMPREPLY+=( "$REPLY" ); done < <( compgen -W "$(printf "'%s' " "${trimmed_paths[@]}")" -- "$cur" )
;;
'edit'*)
while read; do COMPREPLY+=( "$REPLY" ); done < <( compgen -W "relocate username password note url" -- "$cur" )
;;
'copy'*)
while read; do COMPREPLY+=( "$REPLY" ); done < <( compgen -W "username password url note" -- "$cur" )
;;
'add'*)
while read; do COMPREPLY+=( "$REPLY" ); done < <( compgen -W "password note folder" -- "$cur" )
;;
'gen'*)
while read; do COMPREPLY+=( "$REPLY" ); done < <( compgen -W "update" -- "$cur" )
;;
*)
while read; do COMPREPLY+=( "$REPLY" ); done < <( compgen -W "help version tweak add gen edit copy shear sync $(printf "'%s' " "${trimmed_paths[@]}")" -- "$cur" )
;;
esac
} &&
_path_gen && complete -F _sshyp_completions sshyp
Executable
+168
View File
@@ -0,0 +1,168 @@
#!/usr/bin/env python3
from os import listdir, remove, walk
from os.path import expanduser, getmtime, join
from subprocess import CalledProcessError, PIPE, run
from sys import exit as s_exit
# REMOTE
def remote_list_gen(_client_device_name, _remote_dir): # prints all necessary remote data to stdout
# deletions
for _file in listdir(expanduser('~/.config/sshyp/deleted')):
_file_path, _sep, _device = _file.partition('\x1f')
_file_path = _file_path.replace('\x1e', '/')
if _device == _client_device_name:
print(_file_path)
try:
remove(f"{expanduser('~/.config/sshyp/deleted/')}{_file}")
except FileNotFoundError:
pass
print('\x1d')
# folders
for _root, _directories, _files in walk(expanduser('~/.local/share/sshyp')):
for _dir in _directories:
print(f"{_root.replace(expanduser('~'), '')}/{_dir}")
print('\x1d')
get_local_data(_remote_dir, 'server') # titles and mod times
# HYBRID
def delete(_file_path, _target_database): # deletes a file or folder and/or marks it for deletion upon syncing
from shutil import rmtree
try:
if _file_path.endswith('/'):
rmtree(f"{expanduser('~/.local/share/sshyp/')}{_file_path}")
else:
remove(f"{expanduser('~/.local/share/sshyp/')}{_file_path}.gpg")
except FileNotFoundError:
print(f"location does not exist {_target_database}")
if _target_database == 'remotely':
for _device_name in listdir(expanduser('~/.config/sshyp/devices')):
open(expanduser('~/.config/sshyp/deleted/') + _file_path.replace('/', '\x1e') + '\x1f' + _device_name, 'w')
def get_local_data(_directory, _device): # retrieves and returns titles and mod times from the local device
_title_list, _mod_list = [], []
for _root, _directories, _files in walk(_directory):
for _filename in _files:
_title_list.append(join(_root.replace(_directory, '', 1), _filename))
_mod_list.append(int(getmtime(join(_root, _filename))))
if _device == 'server':
for _title in _title_list:
print(_title.rstrip())
for _time in _mod_list:
print(_time)
return _title_list, _mod_list
# LOCAL
def remote_list_fetch(_user_data): # captures and returns all necessary data from the remote server
try:
_remote_data = run(['ssh', '-i', _user_data[5], '-p', _user_data[2], f"{_user_data[0]}@{_user_data[1]}",
f'cd /lib/sshyp; python3 -c \'from sshync import remote_list_gen; remote_list_gen'
f'("{_user_data[6]}", "{_user_data[4]}")\''], stdout=PIPE, text=True, check=True
).stdout.split('\x1d')
except CalledProcessError:
print('\n\u001b[38;5;9merror: failed to connect to the remote server\u001b[0m\n')
_remote_data = ''
s_exit(6)
_deletion_database = _remote_data[0].strip().split('\n')
_folder_database = _remote_data[1].strip().split('\n')
_titles_mods = _remote_data[2].strip().split('\n')
return _deletion_database, _folder_database, _titles_mods[:len(_titles_mods)//2], \
_titles_mods[len(_titles_mods)//2:]
def deletion_sync(_deletion_database, _silent): # checks for and acts upon files and folders marked for deletion
for _file in _deletion_database:
if _file != '':
if _silent != 1:
print(f"\u001b[38;5;208m{_file}\u001b[0m has been sheared, removing...")
delete(_file, 'locally')
def folder_sync(_folder_database): # creates matches of remote folders on the local client
from pathlib import Path
for _folder in _folder_database:
if _folder != '' and not Path(f"{expanduser('~')}{_folder}").is_dir():
print(f"\u001b[38;5;2m{_folder.replace('/.local/share/sshyp/', '')}/\u001b[0m does not exist locally, "
f"creating...")
Path(f"{expanduser('~')}{_folder}").mkdir(mode=0o700, parents=True, exist_ok=True)
def sort_titles_mods(_list_1, _list_2): # creates and returns two lists (of titles and mod times) generated by sorting
# information from two provided 2D lists
_title_list_2_sorted, _mod_list_2_sorted = [], []
# title sorting
for _title in _list_1[0]:
if _title in _list_2[0]:
_title_list_2_sorted.append(_title)
for _title in _list_2[0]:
if _title not in _title_list_2_sorted:
_title_list_2_sorted.append(_title)
# mod time sorting
for _title in _title_list_2_sorted:
_mod_list_2_sorted.append(_list_2[1][_list_2[0].index(_title)])
return _title_list_2_sorted, _mod_list_2_sorted
def make_profile(_profile_dir, _local_dir, _remote_dir, _identity, _ip, _port, _user): # creates a sshync job profile
open(_profile_dir, 'w').write(f"{_user}\n{_ip}\n{_port}\n{_local_dir}\n{_remote_dir}\n{_identity}\n")
def get_profile(_profile_dir): # returns a list of data read from a sshync job profile
try:
_profile_data = open(_profile_dir).readlines()
except (FileNotFoundError, IndexError):
print('\n\u001b[38;5;9merror: the profile does not exist or is corrupted\u001b[0m\n')
_profile_data = None
s_exit(3)
_user = _profile_data[0].rstrip()
_ip = _profile_data[1].rstrip()
_port = _profile_data[2].rstrip()
_local_dir = _profile_data[3].rstrip()
_remote_dir = _profile_data[4].rstrip()
_identity = _profile_data[5].rstrip()
_client_device_id = listdir(expanduser('~/.config/sshyp/devices'))[0].rstrip()
return _user, _ip, _port, _local_dir, _remote_dir, _identity, _client_device_id
def run_profile(_profile_dir, _silent): # runs a sshync job profile
_user_data = get_profile(_profile_dir) # import profile data
# fetch remote lists
_deletion_database, _folder_database, _remote_titles, _remote_mods = remote_list_fetch(_user_data)
_remote_titles_mods = (_remote_titles, _remote_mods)
# sync deletions and folders
deletion_sync(_deletion_database, _silent)
folder_sync(_folder_database)
# sort titles and mods
_index_local = sort_titles_mods(_remote_titles_mods, get_local_data(_user_data[3], 'client'))
_index_remote = sort_titles_mods(_index_local, _remote_titles_mods)
# sync new and updated files
_i = -1
for _title in _index_local[0]:
_i += 1
if _title in _index_remote[0]:
# compare mod times and sync
if int(_index_local[1][_i]) > int(_index_remote[1][_i]):
print(f"\u001b[38;5;4m{_title[:-4]}\u001b[0m is newer locally, uploading...")
run(['scp', '-pqs', '-P', _user_data[2], '-i', _user_data[5], _user_data[3] + _title,
f"{_user_data[0]}@{_user_data[1]}:{_user_data[4]}{'/'.join(_title.split('/')[:-1]) + '/'}"])
elif int(_index_local[1][_i]) < int(_index_remote[1][_i]):
print(f"\u001b[38;5;2m{_title[:-4]}\u001b[0m is newer remotely, downloading...")
run(['scp', '-pqs', '-P', _user_data[2], '-i', _user_data[5],
f"{_user_data[0]}@{_user_data[1]}:{_user_data[4]}{_title}",
f"{_user_data[3]}{'/'.join(_title.split('/')[:-1]) + '/'}"])
else:
print(f"\u001b[38;5;4m{_title[:-4]}\u001b[0m is not on remote server, uploading...")
run(['scp', '-pqs', '-P', _user_data[2], '-i', _user_data[5], _user_data[3] + _title,
f"{_user_data[0]}@{_user_data[1]}:{_user_data[4]}{'/'.join(_title.split('/')[:-1]) + '/'}"])
for _title in _index_remote[0]:
if _title not in _index_local[0]:
print(f"\u001b[38;5;2m{_title[:-4]}\u001b[0m is not in local directory, downloading...")
run(['scp', '-pqs', '-P', _user_data[2], '-i', _user_data[5],
f"{_user_data[0]}@{_user_data[1]}:{_user_data[4]}{_title}",
f"{_user_data[3]}{'/'.join(_title.split('/')[:-1]) + '/'}"])
Executable
+911
View File
@@ -0,0 +1,911 @@
#!/usr/bin/env python3
from os import chmod, environ, listdir, remove, uname, walk
from os.path import expanduser
from pathlib import Path
from random import randint
from shutil import get_terminal_size, move, rmtree
from sshync import delete as offline_delete, run_profile, make_profile, get_profile
from subprocess import CalledProcessError, DEVNULL, PIPE, run
from sys import argv, exit as s_exit
# UTILITY FUNCTIONS
def entry_list_gen(_directory=expanduser('~/.local/share/sshyp/')): # generates and prints full entry list
from textwrap import fill
print('\n\u001b[38;5;0;48;5;15msshyp entries:\u001b[0m\n')
_entry_list, _color_alternator = [], 1
for _entry in sorted(listdir(_directory)):
if Path(f"{_directory}{_entry}").is_file():
if _color_alternator == 1:
_entry_list.append(f"{_entry.replace('.gpg', '')}")
_color_alternator = 2
else:
_entry_list.append(f"\u001b[38;5;8m{_entry.replace('.gpg', '')}\u001b[0m")
_color_alternator = 1
_real = len(' '.join(_entry_list)) - (5.5 * len(_entry_list))
if _real <= get_terminal_size()[0]:
_width = len(' '.join(_entry_list))
else:
_width = (len(' '.join(_entry_list)) / (_real / get_terminal_size()[0]) - 25)
try:
print(fill(' '.join(_entry_list), width=_width) + '\n')
except ValueError:
pass
for _root, _dirs, _files in walk(_directory):
for _dir in sorted(_dirs):
_inner_dir = f"{_root.replace(_directory, '')}/{_dir}"
print(f"\u001b[38;5;15;48;5;238m{_inner_dir}/\u001b[0m")
_entry_list, _color_alternator = [], 1
for _s_root, _s_directories, _s_files in walk(f"{_directory[:-1]}{_inner_dir}"):
for _entry in sorted(_s_files):
if _color_alternator == 1:
_entry_list.append(f"{_entry.replace('.gpg', '')}")
_color_alternator = 2
else:
_entry_list.append(f"\u001b[38;5;8m{_entry.replace('.gpg', '')}\u001b[0m")
_color_alternator = 1
_real = len(' '.join(_entry_list)) - (5.5 * len(_entry_list))
if _real <= get_terminal_size()[0]:
_width = len(' '.join(_entry_list))
else:
_width = (len(' '.join(_entry_list)) / (_real / get_terminal_size()[0]) - 25)
try:
print(fill(' '.join(_entry_list), width=_width) + '\n')
except ValueError:
print('\u001b[38;5;9m-empty directory-\u001b[0m\n')
def entry_reader(_decrypted_entry): # displays the contents of an entry in a readable format
_entry_lines, _notes_flag = open(_decrypted_entry, 'r').readlines(), 0
print()
for _num in range(len(_entry_lines)):
try:
if _num == 0 and _entry_lines[1] != '\n':
print(f"\u001b[38;5;15;48;5;238musername:\u001b[0m\n{_entry_lines[1].strip()}\n")
elif _num == 1 and _entry_lines[0] != '\n':
print(f"\u001b[38;5;15;48;5;238mpassword:\u001b[0m\n{_entry_lines[0].strip()}\n")
elif _num == 2 and _entry_lines[2] != '\n':
print(f"\u001b[38;5;15;48;5;238murl:\u001b[0m\n{_entry_lines[_num].strip()}\n")
elif _num >= 3 and _entry_lines[_num] != '\n' and _notes_flag != 1:
_notes_flag = 1
print(f"\u001b[38;5;15;48;5;238mnotes:\u001b[0m\n{_entry_lines[_num].strip()}")
elif _num >= 3 and _notes_flag == 1:
print(_entry_lines[_num].replace('\n', ''))
if _notes_flag == 1:
try:
_line_test = _entry_lines[_num + 1]
except IndexError:
print()
except IndexError:
if _num == 0:
print(f"\u001b[38;5;15;48;5;238mpassword:\u001b[0m\n{_entry_lines[0]}\n")
def entry_name_fetch(_entry_name_location): # fetches and returns entry name from user input or from provided argument
if type(_entry_name_location) == str:
entry_list_gen()
_entry_name = str(input(_entry_name_location))
else:
_entry_name_split = argument.split(' ')
del _entry_name_split[:_entry_name_location]
_entry_name = ' '.join(_entry_name_split)
if _entry_name.startswith('/'):
return _entry_name.replace('/', '', 1)
else:
return _entry_name.strip()
def string_gen(_complexity, _length): # generates and returns a random string based on input
from random import SystemRandom
import string
if _complexity == 's':
_character_pool = string.ascii_letters + string.digits
elif _complexity == 'f':
_character_pool = string.digits + string.ascii_letters + string.punctuation.replace('/', '').replace('\\', '')\
.replace("'", '').replace('"', '').replace('`', '').replace('~', '')
else:
_character_pool = string.digits + string.ascii_letters + string.punctuation
_min_special, _special = round(.2 * _length), 0
while True:
_gen = ''.join(SystemRandom().choice(_character_pool) for _ in range(_length))
for _character in _gen:
if not _character.isalpha():
_special += 1
if _special >= _min_special:
break
return _gen
def pass_gen(): # prompts the user for necessary information to generate a password and passes it to string_gen
_length = 9
while True:
try:
_length = int(input('password length: '))
except ValueError:
continue
else:
if _length < 1:
continue
else:
break
_complexity = str(input('password complexity - simple (for compatibility) or complex (for security)? (s/C) '))
if _complexity != 's' and _complexity != 'S':
_complexity = 'c'
_gen = string_gen(_complexity.lower(), _length)
return _gen
def shm_gen(_tmp_dir=expanduser('~/.config/sshyp/tmp/')): # creates a temporary directory for entry editing
_shm_folder_gen = string_gen('f', randint(12, 48))
_shm_entry_gen = string_gen('f', randint(12, 48))
Path(_tmp_dir + _shm_folder_gen).mkdir(mode=0o700)
return _shm_folder_gen, _shm_entry_gen
def encrypt(_entry_dir, _shm_folder, _shm_entry, _gpg_id, _tmp_dir=expanduser('~/.config/sshyp/tmp/')):
# encrypts an entry and cleans up the temporary files
run(['gpg', '-qr', str(_gpg_id), '-e', f"{_tmp_dir}{_shm_folder}/{_shm_entry}"])
move(f"{_tmp_dir}{_shm_folder}/{_shm_entry}.gpg", f"{_entry_dir}.gpg")
rmtree(f"{_tmp_dir}{_shm_folder}")
def decrypt(_entry_dir, _shm_folder, _shm_entry, _quick_pass,
_tmp_dir=expanduser('~/.config/sshyp/tmp/')): # decrypts an entry to a temporary directory
if not isinstance(_quick_pass, bool):
_unlock_method = ['gpg', '--pinentry-mode', 'loopback', '--passphrase', _quick_pass, '-qd', '--output']
else:
_unlock_method = ['gpg', '-qd', '--output']
if _shm_folder == 0 and _shm_entry == 0:
_output_target = ['/dev/null', expanduser('~/.config/sshyp/lock.gpg')]
else:
_output_target = [f"{_tmp_dir}{_shm_folder}/{_shm_entry}", f"{_entry_dir}.gpg"]
try:
run(_unlock_method + _output_target, stderr=DEVNULL, check=True)
except CalledProcessError:
if not isinstance(_quick_pass, bool):
print('\n\u001b[38;5;9merror: quick-unlock failed as a result of an incorrect passphrase, an unreachable '
'sshyp server, or an invalid configuration\n\nfalling back to standard unlock\u001b[0m\n')
try:
run(['gpg', '-qd', '--output'] + _output_target, stderr=DEVNULL, check=True)
except CalledProcessError:
print('\n\u001b[38;5;9merror: could not decrypt - ensure the correct gpg key is present\u001b[0m\n')
s_exit(5)
else:
print('\n\u001b[38;5;9merror: could not decrypt - ensure the correct gpg key is present\u001b[0m\n')
s_exit(5)
def determine_decrypt(_entry_dir, _shm_folder, _shm_entry):
if quick_unlock_enabled == 'y':
decrypt(_entry_dir, _shm_folder, _shm_entry, whitelist_verify(port, username_ssh, ip, client_device_id))
else:
decrypt(_entry_dir, _shm_folder, _shm_entry, False)
def optimized_edit(_lines, _edit_data, _edit_line): # ensures an edited entry is optimized for best compatibility
while len(_lines) < _edit_line + 1:
_lines += ['\n']
if _edit_data is not None:
_lines[_edit_line] = _edit_data.strip('\n').rstrip() + '\n'
for _num in range(len(_lines)):
if not _lines[_num].endswith('\n'):
_lines[_num] += '\n'
for _num in reversed(range(len(_lines))):
if _lines[_num] == '\n':
_lines = _lines[:-1]
elif _lines[_num].endswith('\n'):
_lines[_num] = _lines[_num].rstrip()
break
else:
break
return _lines
def edit_note(_shm_folder, _shm_entry, _lines): # edits the note attached to an entry
_reg_lines = _lines[0:3]
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'w').writelines(_lines[3:])
run([editor, f"{tmp_dir}{_shm_folder}/{_shm_entry}-n"])
_new_notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n").readlines()
while len(_reg_lines) < 3:
_reg_lines += ['\n']
_noted_lines = _reg_lines + _new_notes
return _noted_lines
def copy_id_check(_port, _username_ssh, _ip, _client_device_id):
# attempts to connect to the user's server via ssh to register the device for syncing
try:
run(['ssh', '-o', 'ConnectTimeout=3', '-i', expanduser('~/.ssh/sshyp'), '-p', _port, f"{_username_ssh}@{_ip}",
f'python3 -c \'from pathlib import Path; Path("/home/{_username_ssh}/.config/sshyp/devices/'
f'{_client_device_id}").touch(mode=0o400, exist_ok=True)\''], stderr=DEVNULL, check=True)
except CalledProcessError:
print('\n\u001b[38;5;9mwarning: ssh connection could not be made - ensure the public key (~/.ssh/sshyp.pub) is '
'registered on the remote server and that the entered ip, port, and username are correct\n\nsyncing '
'functionality will be disabled until this is addressed\u001b[0m\n')
open(expanduser('~/.config/sshyp/ssh-error'), 'w').write('1')
return 1
open(expanduser('~/.config/sshyp/ssh-error'), 'w').write('0')
return 0
# ARGUMENT-SPECIFIC FUNCTIONS
def tweak(): # runs configuration wizard
from os import symlink
_divider = f"\n{'=' * (get_terminal_size()[0] - int((.5 * get_terminal_size()[0])))}\n\n"
# config directory creation
Path(expanduser('~/.config/sshyp/devices')).mkdir(mode=0o700, parents=True, exist_ok=True)
if not Path(f"{expanduser('~/.config/sshyp/tmp')}").exists():
if uname()[0] == 'Haiku' or uname()[0] == 'FreeBSD':
symlink('/tmp', expanduser('~/.config/sshyp/tmp'))
elif Path("/data/data/com.termux").exists():
symlink('/data/data/com.termux/files/usr/tmp', expanduser('~/.config/sshyp/tmp'))
else:
symlink('/dev/shm', expanduser('~/.config/sshyp/tmp'))
# device type configuration
_device_type = input('\nclient or server installation? (C/s) ')
if _device_type.lower() == 's':
_sshyp_data = ['server']
Path(expanduser('~/.config/sshyp/deleted')).mkdir(mode=0o700, exist_ok=True)
Path(expanduser('~/.config/sshyp/whitelist')).mkdir(mode=0o700, exist_ok=True)
print(f"\n\u001b[4;1mmake sure the ssh service is running and properly configured\u001b[0m")
else:
_sshyp_data = ['client']
Path(expanduser('~/.local/share/sshyp')).mkdir(mode=0o700, parents=True, exist_ok=True)
# gpg configuration
_gpg_gen = input(f"{_divider}sshyp requires the use of a unique gpg key - use an (e)xisting key or (g)enerate a"
f" new one? (E/g) ")
if _gpg_gen.lower() != 'g':
run(['gpg', '-k'])
_sshyp_data += [str(input('gpg key id: '))]
else:
print('\na unique gpg key is being generated for you...')
if not Path(expanduser('~/.config/sshyp/gpg-gen')).is_file():
open(expanduser('~/.config/sshyp/gpg-gen'), 'w').writelines([
'Key-Type: 1\n', 'Key-Length: 4096\n', 'Key-Usage: sign encrypt\n', 'Name-Real: sshyp\n',
'Name-Comment: gpg-sshyp\n', 'Name-Email: https://github.com/rwinkhart/sshyp\n', 'Expire-Date: 0'])
run(['gpg', '--batch', '--generate-key', expanduser('~/.config/sshyp/gpg-gen')])
remove(expanduser('~/.config/sshyp/gpg-gen'))
_sshyp_data += [run(['gpg', '-k'], stdout=PIPE, text=True).stdout.split('\n')[-4].strip()]
# text editor configuration
_sshyp_data += [input(f"{_divider}example input: vim\n\npreferred text editor: ")]
# lock file generation
if Path(expanduser('~/.config/sshyp/lock.gpg')).is_file():
remove(expanduser('~/.config/sshyp/lock.gpg'))
open(expanduser('~/.config/sshyp/lock'), 'w')
run(['gpg', '-qr', str(_sshyp_data[1]), '-e', expanduser('~/.config/sshyp/lock')])
remove(expanduser('~/.config/sshyp/lock'))
# ssh key configuration
_offline_mode = False
_ssh_gen = (input(f"{_divider}make sure the ssh service on the remote server is running and properly "
f"configured\n\nsync support requires a unique ssh key - would you like to have this "
f"automatically generated? (Y/n/o(ffline)) "))
if _ssh_gen.lower() != 'n' and _ssh_gen.lower() != 'o' and _ssh_gen.lower() != 'offline':
Path(f"{expanduser('~')}/.ssh").mkdir(mode=0o700, exist_ok=True)
run(['ssh-keygen', '-t', 'ed25519', '-f', expanduser('~/.ssh/sshyp')])
elif _ssh_gen.lower() == 'n':
print(f"\n\u001b[4;1mensure that the key file you are using is located at "
f"{expanduser('~/.ssh/sshyp')}\u001b[0m")
elif _ssh_gen.lower() == 'o' or _ssh_gen.lower() == 'offline':
_offline_mode = True
print('\nsshyp has been set to offline mode - to enable syncing, run "sshyp tweak" again')
if not _offline_mode:
# ssh ip+port configuration
_ip_port = str(input(f"{_divider}example input: 10.10.10.10:9999\n\nip and ssh port of sshyp server: "))
_ip, _sep, _port = _ip_port.partition(':')
# ssh user configuration
_username_ssh = str(input('\nusername of the remote server: '))
# sshync profile generation
make_profile(expanduser('~/.config/sshyp/sshyp.sshync'),
expanduser('~/.local/share/sshyp/'), f"/home/{_username_ssh}/.local/share/sshyp/",
expanduser('~/.ssh/sshyp'), _ip, _port, _username_ssh)
# device id configuration
for _id in listdir(expanduser('~/.config/sshyp/devices')): # remove existing device id
remove(f"{expanduser('~/.config/sshyp/devices/')}{_id}")
print(f"{_divider}\u001b[4;1mimportant:\u001b[0m this id \u001b[4;1mmust\u001b[0m be unique amongst your "
f"client devices\n\nthis is used to keep track of database syncing and quick-unlock permissions\n")
_device_id_prefix = str(input('device id: ')) + '-'
_device_id_suffix = string_gen('f', randint(24, 48))
_device_id = _device_id_prefix + _device_id_suffix
open(f"{expanduser('~/.config/sshyp/devices/')}{_device_id}", 'w')
# quick-unlock configuration
print(f"{_divider}this allows you to use a shorter version of your gpg key password and\n"
f"requires a constant connection to your sshyp server to authenticate")
_sshyp_data += [input('\nenable quick-unlock? (y/N) ').lower()]
if _sshyp_data[3] == 'y':
print(f"\nquick-unlock has been enabled client-side - in order for this device to be able to read "
f"entries,\nyou must first login to the sshyp server and run:\n\nsshyp whitelist setup "
f"(if not already done)\nsshyp whitelist add '{_device_id}'")
# test server connection and attempt to register device id
copy_id_check(_port, _username_ssh, _ip, _device_id)
elif Path(expanduser('~/.config/sshyp/sshyp.sshync')).is_file():
remove(expanduser('~/.config/sshyp/sshyp.sshync'))
# write main config file (sshyp-data)
with open(expanduser('~/.config/sshyp/sshyp-data'), 'w') as _config_file:
_lines = 0
for _item in _sshyp_data:
_lines += 1
_config_file.write(_item + '\n')
while _lines < 4:
_lines += 1
_config_file.write('n')
print(f"{_divider}configuration complete\n")
def print_info(): # prints help text based on argument
if argument_list[1] == 'help' or argument_list[1] == '--help' or argument_list[1] == '-h':
print('\n\u001b[1msshyp copyright (c) 2021-2022 randall winkhart\u001b[0m\n')
print("this is free software, and you are welcome to redistribute it under certain conditions;\nthis program "
"comes with absolutely no warranty;\ntype 'sshyp license' for details")
if device_type == 'client':
print('\n\u001b[1musage:\u001b[0m sshyp [option [flag] [<entry name>]] | [/<entry name>]\n')
print('\u001b[1moptions:\u001b[0m')
print('help/--help/-h bring up this menu')
print('version/-v display sshyp version info')
print('tweak configure sshyp')
print('add add an entry')
print('gen generate a new password')
print('edit edit an existing entry')
print('copy copy details of an entry to your clipboard')
print('shear/-rm delete an existing entry')
print('sync/-s manually sync the entry directory via sshync')
print('\n\u001b[1mflags:\u001b[0m')
print('add:')
print(' password/-p add a password entry')
print(' note/-n add a note entry')
print(' folder/-f add a new folder for entries')
print('edit:')
print(' rename/relocate/-r rename or relocate an entry')
print(' username/-u change the username of an entry')
print(' password/-p change the password of an entry')
print(' url/-l change the url attached to an entry')
print(' note/-n change the note attached to an entry')
print('copy:')
print(' username/-u copy the username of an entry to your clipboard')
print(' password/-p copy the password of an entry to your clipboard')
print(' url/-l copy the url of an entry to your clipboard')
print(' note/-n copy the note of an entry to your clipboard')
print('gen:')
print(' update/-u generate a password for an existing entry')
print("\n\u001b[1mtip:\u001b[0m you can quickly read an entry with 'sshyp /<entry name>'\n")
else:
print('\n\u001b[1musage:\u001b[0m sshyp [option [flag] [<device id>]]\n')
print('\u001b[1moptions:\u001b[0m')
print('help/--help/-h bring up this menu')
print('version/-v display sshyp version info')
print('tweak configure sshyp')
print('whitelist manage the quick-unlock whitelist')
print('\n\u001b[1mflags:\u001b[0m')
print('whitelist:')
print(' setup set up the quick-unlock whitelist')
print(' list/-l view all registered device ids and their quick-unlock whitelist status')
print(' add whitelist a device id for quick-unlock')
print(' delete/del remove a device id from the quick-unlock whitelist\n')
elif argument_list[1] == 'version' or argument_list[1] == '-v':
print('\nsshyp is a simple, self-hosted, sftp-synchronized password manager\nfor unix(-like) systems (haiku/'
'freebsd/linux/termux)\n\nsshyp is a viable alternative to (and compatible with) pass/password-store\n')
print(" .. \u001b[38;5;9m♥♥ ♥♥\u001b[0m ..\n .''.''/()\\ \u001b[38;5;10m"
"♥♥♥♥♥♥♥\u001b[0m /()\\''.''.\n * : \u001b[38;5;9m♥♥♥♥♥\u001b[0m : *"
"\n `..'..' \u001b[38;5;10m♥♥♥\u001b[0m `..'..'\n // \\\\ "
"\u001b[38;5;9m♥\u001b[0m // \\\\")
print('\u001b[38;5;7;48;5;8m<><><><><><><><><><><><><><><><><><><><><><><><><><><><>\u001b[0m')
print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m '
'\u001b[38;5;7;48;5;8m/\u001b[0m')
print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m \u001b[38;5;15;48;5;8msshyp copyright (c) 2021-2022 '
'randall winkhart\u001b[38;5;15;48;5;15m \u001b[38;5;7;48;5;8m/\u001b[0m')
print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m '
'\u001b[38;5;7;48;5;8m/\u001b[0m')
print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m \u001b[38;5;15;48;5;8mversion 1.3.0'
'\u001b[38;5;15;48;5;15m \u001b[38;5;7;48;5;8m/\u001b[0m')
print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m \u001b[38;5;15;48;5;8mthe serious shepherd '
'update\u001b[38;5;15;48;5;15m \u001b[38;5;7;48;5;8m/\u001b[0m')
print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m '
'\u001b[38;5;7;48;5;8m/\u001b[0m')
print('\u001b[38;5;7;48;5;8m<><><><><><><><><><><><><><><><><><><><><><><><><><><><>\u001b[0m\n')
print('see https://github.com/rwinkhart/sshyp for more information\n')
elif argument_list[1] == 'license':
print('\nThis program is free software: you can redistribute it and/or modify it under the terms\nof version 3 '
'(only) of the GNU General Public License as published by the Free Software Foundation.\n\nThis program '
'is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\nwithout even the implied '
'warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\nSee the GNU General Public License for'
' more details.\n\nhttps://opensource.org/licenses/GPL-3.0\n')
elif argument_list[1] == 'add':
print('\n\u001b[1musage:\u001b[0m sshyp add [flag [<entry name>]]\u001b[0m\n')
print('\u001b[1mflags:\u001b[0m')
print('add:')
print(' password/-p add a password entry')
print(' note/-n add a note entry')
print(' folder/-f add a new folder for entries\n')
elif argument_list[1] == 'edit':
print('\n\u001b[1musage:\u001b[0m sshyp edit [flag [<entry name>]]\u001b[0m\n')
print('\u001b[1mflags:\u001b[0m')
print('edit:')
print(' rename/relocate/-r rename or relocate an entry')
print(' username/-u change the username of an entry')
print(' password/-p change the password of an entry')
print(' url/-l change the url attached to an entry')
print(' note/-n change the note attached to an entry\n')
elif argument_list[1] == 'copy':
print('\n\u001b[1musage:\u001b[0m sshyp copy [flag [<entry name>]]\u001b[0m\n')
print('\u001b[1mflags:\u001b[0m')
print('copy:')
print(' username/-u copy the username of an entry to your clipboard')
print(' password/-p copy the password of an entry to your clipboard')
print(' url/-l copy the url of an entry to your clipboard')
print(' note/-n copy the note of an entry to your clipboard\n')
elif argument_list[1] == 'whitelist':
if device_type == 'server':
print('\n\u001b[1musage:\u001b[0m sshyp whitelist [flag [<device id>]]\u001b[0m\n')
print('\u001b[1mflags:\u001b[0m')
print('whitelist:')
print(' setup set up the quick-unlock whitelist')
print(' list/-l view all registered device ids and their quick-unlock whitelist status')
print(' add whitelist a device id for quick-unlock')
print(' delete/del remove a device id from the quick-unlock whitelist\n')
else:
print('\n\u001b[38;5;9merror: argument (whitelist) only available on server\u001b[0m\n')
s_exit(0)
def no_arg(): # displays a list of entries and gives an option to select one for viewing
print("\nfor a list of usable commands, run 'sshyp help'")
_entry_name = entry_name_fetch('entry to read: ')
if not Path(f"{directory}{_entry_name}.gpg").exists():
print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) does not exist\u001b[0m\n")
s_exit(3)
_shm_folder, _shm_entry = shm_gen()
determine_decrypt(directory + _entry_name, _shm_folder, _shm_entry)
entry_reader(f"{tmp_dir}{_shm_folder}/{_shm_entry}")
rmtree(f"{tmp_dir}{_shm_folder}")
def read_shortcut(): # shortcut to quickly read an entry
if not Path(f"{directory}{argument.replace('/', '', 1)}.gpg").exists():
print(f"\n\u001b[38;5;9merror: entry ({argument.replace('/', '', 1)}) does not exist\u001b[0m\n")
s_exit(3)
_shm_folder, _shm_entry = shm_gen()
determine_decrypt(directory + argument.replace('/', '', 1), _shm_folder, _shm_entry)
entry_reader(f"{tmp_dir}{_shm_folder}/{_shm_entry}")
rmtree(f"{tmp_dir}{_shm_folder}")
def sync(): # calls sshync to sync changes to the user's server
print('\nsyncing entries with the server device...\n')
# set permissions before uploading
for _root, _dirs, _files in walk(expanduser('~/.local/share/sshyp')):
for _path in _root.split('\n'):
chmod(_path, 0o700)
for _file in _files:
chmod(_root + '/' + _file, 0o600)
run_profile(expanduser('~/.config/sshyp/sshyp.sshync'), silent_sync)
def whitelist_setup(): # takes input from the user to set up quick-unlock password
_gpg_password_temp = str(input('\nfull gpg passphrase: '))
_half_length = int(len(_gpg_password_temp)/2)
try:
_short_password_length = int(input(f"\nquick unlock pin length (must be half the length of gpg password or "
f"less) ({_half_length}): "))
if _short_password_length > _half_length:
_short_password_length = _half_length
except ValueError:
_short_password_length = _half_length
_i, _quick_unlock_password, _quick_unlock_password_excluded = 0, '', ''
for _char in _gpg_password_temp:
if _i % 2 == 1 and _i < _short_password_length*2:
_quick_unlock_password += _char
else:
_quick_unlock_password_excluded += _char
_i += 1
# create assembly key
open(expanduser('~/.config/sshyp/gpg-gen'), 'w').writelines([
'Key-Type: 1\n', 'Key-Length: 4096\n', 'Key-Usage: sign encrypt\n', 'Name-Real: sshyp\n',
'Name-Comment: gpg-sshyp-whitelist\n', 'Name-Email: https://github.com/rwinkhart/sshyp\n', 'Expire-Date: 0'])
run(['gpg', '-q', '--pinentry-mode', 'loopback', '--batch', '--generate-key', '--passphrase',
_quick_unlock_password, expanduser('~/.config/sshyp/gpg-gen')])
remove(expanduser('~/.config/sshyp/gpg-gen'))
_gpg_id = run(['gpg', '-k'], stdout=PIPE, text=True).stdout.split('\n')[-4].strip()
# encrypt excluded with the assembly key
_shm_folder, _shm_entry = shm_gen()
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w').write(_quick_unlock_password_excluded)
encrypt(expanduser('~/.config/sshyp/excluded'), _shm_folder, _shm_entry, _gpg_id)
print(f"\nyour quick-unlock passphrase: {_quick_unlock_password}")
def whitelist_verify(_port, _username_ssh, _ip, _client_device_id):
# checks the user's whitelist status and fetches the full gpg key password if possible
try:
run(['gpg', '--pinentry-mode', 'cancel', '-qd', '--output', '/dev/null',
expanduser('~/.config/sshyp/lock.gpg')], stderr=DEVNULL, check=True)
return False
except CalledProcessError:
_i, _full_password = 0, ''
_server_whitelist = run(['ssh', '-i', expanduser('~/.ssh/sshyp'), '-p', _port, f"{_username_ssh}@{_ip}",
f'python3 -c \'from os import listdir; print(*listdir("/home/{_username_ssh}'
f'/.config/sshyp/whitelist"))\''], stdout=PIPE, text=True).stdout.rstrip().split(' ')
for _device_id in _server_whitelist:
if _device_id == _client_device_id:
from getpass import getpass
_quick_unlock_password = getpass(prompt='\nquick-unlock passphrase: ')
_quick_unlock_password_excluded = \
run(['ssh', '-i', expanduser('~/.ssh/sshyp'), '-p', _port, f"{_username_ssh}@{_ip}",
f"gpg --pinentry-mode loopback --passphrase '{_quick_unlock_password}' "
f"-qd ~/.config/sshyp/excluded.gpg"], stdout=PIPE, text=True).stdout.rstrip()
while _i < len(_quick_unlock_password_excluded):
try:
_full_password += _quick_unlock_password_excluded[_i]
except IndexError:
pass
try:
_full_password += _quick_unlock_password[_i]
except IndexError:
pass
_i += 1
break
return _full_password
def whitelist_list(): # shows the quick-unlock whitelist status of device ids
_whitelisted_ids = listdir(expanduser('~/.config/sshyp/whitelist'))
_device_ids = listdir(expanduser('~/.config/sshyp/devices'))
print('\n\u001b[1mquick-unlock whitelisted device ids:\u001b[0m')
for _id in _whitelisted_ids:
print(_id)
print('\n\u001b[1mother registered device ids:\u001b[0m')
for _id in _device_ids:
if _id not in _whitelisted_ids:
print(_id)
print()
def whitelist_manage(): # adds or removes quick-unlock whitelisted device ids
if len(argument_list) == 3:
whitelist_list()
_device_id = input('device id: ')
else:
_argument_split = argument.split(' ')
del _argument_split[:2]
_device_id = ' '.join(_argument_split)
if argument_list[2] == 'add':
if _device_id in listdir(expanduser('~/.config/sshyp/devices')):
open(expanduser(f"~/.config/sshyp/whitelist/{_device_id}"), 'w').write('')
whitelist_list()
else:
print(f"\n\u001b[38;5;9merror: device id ({_device_id}) is not registered\u001b[0m\n")
s_exit(2)
elif Path(expanduser(f"~/.config/sshyp/whitelist/{_device_id}")).is_file():
remove(expanduser(f"~/.config/sshyp/whitelist/{_device_id}"))
whitelist_list()
def add_entry(): # adds a new entry
_shm_folder, _shm_entry = None, None # sets base-line values to avoid errors
if len(argument_list) < 4:
_entry_name = entry_name_fetch('name of new entry: ')
else:
_entry_name = entry_name_fetch(2)
if Path(f"{directory}{_entry_name}.gpg").is_file():
print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) already exists\u001b[0m\n")
s_exit(4)
if argument_list[2] == 'note' or argument_list[2] == '-n':
_shm_folder, _shm_entry = shm_gen()
run([editor, f"{tmp_dir}{_shm_folder}/{_shm_entry}-n"])
_notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'r').read()
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w').writelines(optimized_edit(['', '', '', _notes], None, -1))
elif argument_list[2] == 'password' or argument_list[2] == '-p':
_username = str(input('username: '))
_password = str(input('password: '))
_url = str(input('url: '))
_add_note = input('add a note to this entry? (y/N) ')
_shm_folder, _shm_entry = shm_gen()
if _add_note.lower() == 'y':
run([editor, f"{tmp_dir}{_shm_folder}/{_shm_entry}-n"])
_notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'r').read()
else:
_notes = ''
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w')\
.writelines(optimized_edit([_password, _username, _url, _notes], None, -1))
print('\n\u001b[1mentry preview:\u001b[0m')
entry_reader(f"{tmp_dir}{_shm_folder}/{_shm_entry}")
encrypt(directory + _entry_name, _shm_folder, _shm_entry, gpg_id)
def add_folder(): # creates a new folder
if len(argument_list) < 4:
_entry_name = entry_name_fetch('name of new folder: ')
else:
_entry_name = entry_name_fetch(2)
Path(directory + _entry_name).mkdir(mode=0o700, parents=True, exist_ok=True)
if ssh_error != 1:
run(['ssh', '-i', expanduser('~/.ssh/sshyp'), '-p', port, f"{username_ssh}@{ip}",
f'python3 -c \'from pathlib import Path; Path("{directory_ssh}{_entry_name}")'
f'.mkdir(mode=0o700, parents=True, exist_ok=True)\''])
def rename(): # renames an entry or folder
from shutil import copy
if argument == 'edit rename' or argument == 'edit relocate' or argument == 'edit -r':
_entry_name = entry_name_fetch('entry/folder to rename/relocate: ')
else:
_entry_name = entry_name_fetch(2)
if not Path(f"{directory}{_entry_name}.gpg").is_file() and not Path(f"{directory}{_entry_name}").is_dir():
print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) does not exist\u001b[0m\n")
s_exit(3)
_new_name = entry_name_fetch('new name: ')
if Path(f"{directory}{_new_name}.gpg").is_file() or Path(f"{directory}{_new_name}").is_dir():
print(f"\n\u001b[38;5;9merror: ({_new_name}) already exists\u001b[0m\n")
s_exit(4)
if _entry_name.endswith('/'):
if ssh_error != 1:
Path(f"{directory}{_new_name}").mkdir(mode=0o700, parents=True, exist_ok=True)
run(['ssh', '-i', expanduser('~/.ssh/sshyp'), '-p', port, f"{username_ssh}@{ip}",
f'python3 -c \'from pathlib import Path; Path("{directory_ssh}{_new_name}")'
f'.mkdir(mode=0o700, parents=True, exist_ok=True)\''])
else:
move(f"{directory}{_entry_name}", f"{directory}{_new_name}")
else:
if ssh_error != 1:
copy(f"{directory}{_entry_name}.gpg", f"{directory}{_new_name}.gpg")
else:
move(f"{directory}{_entry_name}.gpg", f"{directory}{_new_name}.gpg")
if ssh_error != 1:
run(['ssh', '-i', expanduser('~/.ssh/sshyp'), '-p', port, f"{username_ssh}@{ip}",
f'cd /lib/sshyp; python3 -c \'from sshync import delete; delete("{_entry_name}", "remotely")\''])
def edit(): # edits the contents of an entry
_shm_folder, _shm_entry, _detail, _edit_line = None, None, None, None # sets values to avoid PEP8 warnings
if len(argument_list) < 4:
_entry_name = entry_name_fetch('entry to edit: ')
else:
_entry_name = entry_name_fetch(2)
if not Path(f"{directory}{_entry_name}.gpg").is_file():
print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) does not exist\u001b[0m\n")
s_exit(3)
_shm_folder, _shm_entry = shm_gen()
determine_decrypt(directory + _entry_name, _shm_folder, _shm_entry)
if argument_list[2] == 'username' or argument_list[2] == '-u':
_detail, _edit_line = str(input('username: ')), 1
elif argument_list[2] == 'password' or argument_list[2] == '-p':
_detail, _edit_line = str(input('password: ')), 0
elif argument_list[2] == 'url' or argument_list[2] == '-l':
_detail, _edit_line = str(input('url: ')), 2
if argument_list[2] == 'note' or argument_list[2] == '-n':
_edit_line = 2
_new_lines = optimized_edit(edit_note(_shm_folder, _shm_entry,
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'r').readlines()), None, -1)
else:
_new_lines = optimized_edit(open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'r').readlines(), _detail, _edit_line)
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w').writelines(_new_lines)
remove(f"{directory}{_entry_name}.gpg")
print('\n\u001b[1mentry preview:\u001b[0m')
entry_reader(f"{tmp_dir}{_shm_folder}/{_shm_entry}")
encrypt(directory + _entry_name, _shm_folder, _shm_entry, gpg_id)
def gen(): # generates a password for a new or an existing entry
_username, _url, _notes = None, None, None # sets base-line values to avoid errors
if argument == 'gen update' or argument == 'gen -u' or argument == 'gen':
_entry_name = entry_name_fetch('name of entry: ')
elif argument_list[2] == 'update' or argument_list[2] == '-u':
_entry_name = entry_name_fetch(2)
if not Path(f"{directory}{_entry_name}.gpg").is_file():
print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) does not exist\u001b[0m\n")
s_exit(3)
else:
_entry_name = entry_name_fetch(1)
if len(argument_list) == 2 or (not argument_list[2] == 'update' and not argument_list[2] == '-u'):
if Path(f"{directory}{_entry_name}.gpg").is_file():
print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) already exists\u001b[0m\n")
s_exit(4)
_username = str(input('username: '))
_password = pass_gen()
_url = str(input('url: '))
_add_note = input('add a note to this entry? (y/N) ')
_shm_folder, _shm_entry = shm_gen()
if _add_note.lower() == 'y':
run([editor, f"{tmp_dir}{_shm_folder}/{_shm_entry}-n"])
_notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'r').read()
else:
_notes = ''
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w')\
.writelines(optimized_edit([_password, _username, _url, _notes], None, -1))
else:
_shm_folder, _shm_entry = shm_gen()
determine_decrypt(directory + _entry_name, _shm_folder, _shm_entry)
_new_lines = optimized_edit(open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'r').readlines(), pass_gen(), 0)
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w').writelines(_new_lines)
remove(f"{directory}{_entry_name}.gpg")
print('\n\u001b[1mentry preview:\u001b[0m')
entry_reader(f"{tmp_dir}{_shm_folder}/{_shm_entry}")
encrypt(directory + _entry_name, _shm_folder, _shm_entry, gpg_id)
def copy_data(): # copies a specified field of an entry to the clipboard
from subprocess import Popen
if len(argument_list) < 4:
_entry_name = entry_name_fetch('entry to copy: ')
else:
_entry_name = entry_name_fetch(2)
if not Path(f"{directory}{_entry_name}.gpg").is_file():
print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) does not exist\u001b[0m\n")
s_exit(3)
_shm_folder, _shm_entry = shm_gen()
determine_decrypt(directory + _entry_name, _shm_folder, _shm_entry)
_copy_line, _index = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'r').readlines(), 0
if argument_list[2] == 'username' or argument_list[2] == '-u':
_index = 1
elif argument_list[2] == 'password' or argument_list[2] == '-p':
_index = 0
elif argument_list[2] == 'url' or argument_list[2] == '-l':
_index = 2
elif argument_list[2] == 'note' or argument_list[2] == '-n':
_index = 3
if 'WAYLAND_DISPLAY' in environ: # Wayland clipboard detection
run(['wl-copy', _copy_line[_index].rstrip()])
Popen('sleep 30; wl-copy -c', shell=True)
elif uname()[0] == 'Haiku': # Haiku clipboard detection
run(['clipboard', '-c', _copy_line[_index].rstrip()])
Popen('sleep 30; clipboard -r', shell=True)
elif Path("/data/data/com.termux").exists(): # Termux (Android) clipboard detection
run(['termux-clipboard-set', _copy_line[_index].rstrip()])
Popen("sleep 30; termux-clipboard-set ''", shell=True)
else: # X11 clipboard detection
run(['xclip', '-sel', 'c'], stdin=Popen(['echo', '-n', _copy_line[_index].rstrip()], stdout=PIPE).stdout)
Popen("sleep 30; echo -n '' | xclip -sel c", shell=True)
rmtree(f"{tmp_dir}{_shm_folder}")
def remove_data(): # deletes an entry from the server and flags it for local deletion on sync
if argument == 'shear' or argument == '-rm':
_entry_name = entry_name_fetch('entry/folder to shear: ')
else:
_entry_name = entry_name_fetch(1)
determine_decrypt(expanduser('~/.config/sshyp/lock.gpg'), 0, 0)
if ssh_error != 1:
run(['ssh', '-i', expanduser('~/.ssh/sshyp'), '-p', port, f"{username_ssh}@{ip}",
f'cd /lib/sshyp; python3 -c \'from sshync import delete; delete("{_entry_name}", "remotely")\''])
else:
offline_delete(_entry_name, 'locally')
if __name__ == "__main__":
try:
silent_sync, ssh_error = 0, 0
# retrieve typed argument
argument_list = argv
argument = ' '.join(argument_list[1:])
# import saved userdata
device_type = ''
if argument != 'tweak':
tmp_dir = expanduser('~/.config/sshyp/tmp/')
try:
sshyp_data = open(expanduser('~/.config/sshyp/sshyp-data')).readlines()
device_type = sshyp_data[0].rstrip()
if device_type == 'client':
directory = expanduser('~/.local/share/sshyp/')
gpg_id = sshyp_data[1].rstrip()
editor = sshyp_data[2].rstrip()
quick_unlock_enabled = sshyp_data[3].rstrip()
if Path(expanduser('~/.config/sshyp/sshyp.sshync')).is_file():
ssh_info = get_profile(expanduser('~/.config/sshyp/sshyp.sshync'))
username_ssh = ssh_info[0].rstrip()
ip = ssh_info[1].rstrip()
port = ssh_info[2].rstrip()
directory_ssh = str(ssh_info[4].rstrip())
client_device_id = listdir(expanduser('~/.config/sshyp/devices'))[0].rstrip()
ssh_error = int(open(expanduser('~/.config/sshyp/ssh-error')).read().rstrip())
if ssh_error != 0:
ssh_error = copy_id_check(port, username_ssh, ip, client_device_id)
else:
ssh_error = 1
elif len(argument_list) <= 1 or (argument_list[1] != "help" and argument_list[1] != "--help" and
argument_list[1] != "-h" and argument_list[1] != "license" and
argument_list[1] != "version" and argument_list[1] != "-v" and
argument_list[1] != "whitelist"):
print(f"\n\u001b[38;5;9merror: invalid server argument - run 'sshyp help' to "
f"list usable commands\u001b[0m\n")
s_exit(1)
except (FileNotFoundError, IndexError):
print('\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')
print("not all necessary configuration files are present - please run 'sshyp tweak'")
print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n')
s_exit(2)
else:
tweak()
s_exit(0)
# run function based on arguments
if argument.startswith('/'):
read_shortcut()
elif argument == '':
no_arg()
elif argument == 'help' or argument == '--help' or argument == '-h' or argument == 'license' or argument \
== 'version' or argument == '-v':
print_info()
elif argument_list[1] == 'whitelist':
if device_type == 'server':
if len(argument_list) == 2:
print_info()
elif argument_list[2] == 'list' or argument_list[2] == '-l':
whitelist_list()
elif argument_list[2] == 'add' or argument_list[2] == 'delete' or argument_list[2] == 'del':
whitelist_manage()
elif argument_list[2] == 'setup':
whitelist_setup()
else:
print_info()
else:
print_info()
elif argument_list[1] == 'add':
if len(argument_list) == 2:
print_info()
elif argument_list[2] == 'note' or argument_list[2] == '-n' or argument_list[2] == 'password' or \
argument_list[2] == '-p':
add_entry()
elif argument_list[2] == 'folder' or argument_list[2] == '-f':
add_folder()
else:
print_info()
elif argument_list[1] == 'edit':
if len(argument_list) == 2:
print_info()
elif argument_list[2] == 'rename' or argument_list[2] == 'relocate' or argument_list[2] == '-r':
silent_sync = 1
rename()
elif argument_list[2] == 'username' or argument_list[2] == '-u' or argument_list[2] == 'password' or \
argument_list[2] == '-p' or argument_list[2] == 'url' or argument_list[2] == '-l' or \
argument_list[2] == 'note' or argument_list[2] == '-n':
edit()
else:
print_info()
elif argument_list[1] == 'gen':
gen()
elif argument_list[1] == 'copy':
if len(argument_list) == 2:
print_info()
elif argument_list[2] == 'username' or argument_list[2] == '-u' or argument_list[2] == 'password' or \
argument_list[2] == '-p' or argument_list[2] == 'url' or argument_list[2] == '-l' or \
argument_list[2] == 'note' or argument_list[2] == '-n':
try:
copy_data()
except IndexError:
print(f"\n\u001b[38;5;9merror: field does not exist in entry\u001b[0m\n")
s_exit(3)
else:
print_info()
elif argument_list[1] == 'shear' or argument_list[1] == '-rm':
remove_data()
elif argument_list[1] != 'sync' and argument_list[1] != '-s':
print(f"\n\u001b[38;5;9merror: invalid argument - run 'sshyp help' to list usable commands\u001b[0m\n")
s_exit(1)
# sync if any changes were made
if len(argument_list) > 1 and ssh_error == 0 \
and ((argument_list[1] == 'sync' or argument_list[1] == '-s' or argument_list[1] == 'gen' or
argument_list[1] == 'shear' or argument_list[1] == '-rm') or
((argument_list[1] == 'add' or argument_list[1] == 'edit') and len(argument_list) > 2)):
sync()
except KeyboardInterrupt:
print('\n')
+190 -164
View File
@@ -1,32 +1,80 @@
#!/bin/bash
#!/usr/bin/env bash
# This script packages sshyp (from source) for various UNIX(-like) environments.
# Dependencies (Arch Linux): dpkg (packaging for Debian/Termux), freebsd-pkg (packaging for FreeBSD)
# Dependencies (Fedora) (can only package for self): rpmdevtools
# Dependencies (Alpine): xz (not part of a basic installation, needed for generic package creation and unpacking for abuild), alpine-sdk (required to build the .apk file from the generated APKBUILD), bash
# NOTE It is recommended to instead use the latest officially packaged and tagged release.
echo -e '\nOptions (please enter the number only):'
echo -e '\nPackage Formats:\n\n1. Haiku\n2. Debian&Ubuntu Linux\n3. Fedora Linux\n4. FreeBSD\n5. Termux\n6. Generic (used for PKGBUILD/APKBUILD)'
echo -e '\nBuild Scripts:\n\n7. Arch Linux (PKGBUILD)\n8. Alpine Linux (APKBUILD)'
echo -e '\nOther:\n\n9. All (generates all distribution packages (excluding Haiku, Fedora, and Alpine, as these must be packaged on their respective distributions) and build scripts)\n'
read -n 1 -r -p "Distribution: " distro
echo -e '\n\nThe value entered in this field will only affect the version reported to the package manager. The latest source is used regardless.\n'
read -r -p "Version number: " version
echo -e '\nThe value entered in this field will only affect the revision number for build scripts.\n'
read -r -p "Revision number: " revision
if [ "$distro" == "7" ] || [ "$distro" == "8" ] || [ "$distro" == "9" ]; then
source='https://github.com/rwinkhart/sshyp/releases/download/v$pkgver/sshyp-$pkgver.tar.xz'
version=$(sed -n '1{p;q}' share/doc/sshyp/changelog | cut -c8-)
if [ -z "$2" ]; then
revision=1
else
revision="$2"
fi
mkdir -p packages
_create_generic() {
echo -e '\npackaging as generic...\n'
mkdir -p output/generictemp/usr/{bin,lib/sshyp,share/{man/man1,bash-completion/completions}}
cp -r lib/. output/generictemp/usr/lib/sshyp/
ln -s /usr/lib/sshyp/sshyp.py output/generictemp/usr/bin/sshyp
cp -r share output/generictemp/usr/
cp extra/sshyp-completion.bash output/generictemp/usr/share/bash-completion/completions/sshyp
cp extra/manpage output/generictemp/usr/share/man/man1/sshyp.1
gzip output/generictemp/usr/share/man/man1/sshyp.1
tar -C output/generictemp -cvJf output/sshyp-"$version".tar.xz usr/
rm -rf output/generictemp
sha512="$(sha512sum output/sshyp-"$version".tar.xz | awk '{print $1;}')"
echo -e "\nsha512 sum:\n$sha512"
echo -e "\ngeneric packaging complete\n"
} &&
if [ "$distro" == "1" ]; then
echo -e '\nPackaging for Haiku...\n'
mkdir -p packages/haikutemp/documentation/{man/man1,packages/sshyp}
_create_pkgbuild() {
source='https://github.com/rwinkhart/sshyp/releases/download/v$pkgver/sshyp-$pkgver.tar.xz'
echo -e '\ngenerating PKGBUILD...'
echo "# Maintainer: Randall Winkhart <idgr at tutanota dot com>
pkgname=sshyp
pkgver="$version"
pkgrel="$revision"
pkgdesc='A light-weight, self-hosted, synchronized password manager'
url='https://github.com/rwinkhart/sshyp'
arch=('any')
license=('GPL-3.0-only')
depends=(python gnupg openssh xclip wl-clipboard)
optdepends=('bash-completion: bash completion support')
source=(\""$source"\")
sha512sums=('"$sha512"')
package() {
tar xf sshyp-"\"\$pkgver\"".tar.xz -C "\"\${pkgdir}\""
}
" > output/PKGBUILD
echo -e "\nPKGBUILD generated\n"
} &&
_create_apkbuild() {
echo -e '\ngenerating APKBUILD...'
echo "# Maintainer: Randall Winkhart <idgr@tutanota.com>
pkgname=sshyp
pkgver="$version"
pkgrel="$((revision-1))"
pkgdesc='A light-weight, self-hosted, synchronized password manager'
options=!check
url='https://github.com/rwinkhart/sshyp'
arch='noarch'
license='GPL-3.0-only'
depends='python3 gnupg openssh xclip wl-clipboard'
source=\""$source"\"
package() {
mkdir -p "\"\$pkgdir\""
cp -r "\"\$srcdir/usr/"\" "\"\$pkgdir\""
}
sha512sums=\"
"$sha512' 'sshyp-\"\$pkgver\".tar.xz"
\"
" > output/APKBUILD
echo -e "\nAPKBUILD generated\n"
} &&
_create_hpkg() {
echo -e '\npackaging for Haiku...\n'
mkdir -p output/haikutemp/{bin,lib/sshyp,documentation/{packages/sshyp,man/man1},data/bash-completion/completions}
echo "name sshyp
version "$version"-"$revision"
architecture any
@@ -47,33 +95,33 @@ provides {
requires {
gnupg
openssh
python3
python310
}
urls {
\"https://github.com/rwinkhart/sshyp\"
}
" > packages/haikutemp/.PackageInfo
cp -r bin packages/haikutemp/
sed -i '1 s/.*/#!\/bin\/env\ python3/' packages/haikutemp/bin/sshync.py
sed -i '1 s/.*/#!\/bin\/env\ python3/' packages/haikutemp/bin/sshyp.py
sed -i '1 s/.*/#!\/bin\/env\ python3/' packages/haikutemp/bin/sshypRemote.py
ln -s /bin/sshyp.py packages/haikutemp/bin/sshyp
cp -r share/doc/sshyp/ packages/haikutemp/documentation/packages/
cp -r share/licenses/sshyp/ packages/haikutemp/documentation/packages/
cp extra/manpage packages/haikutemp/documentation/man/man1/sshyp.1
gzip packages/haikutemp/documentation/man/man1/sshyp.1
cd packages/haikutemp
" > output/haikutemp/.PackageInfo
cp -r lib/. output/haikutemp/lib/sshyp/
sed -i '1 s/.*/#!\/bin\/env\ python3.10/' output/haikutemp/lib/sshyp/sshync.py
sed -i '1 s/.*/#!\/bin\/env\ python3.10/' output/haikutemp/lib/sshyp/sshyp.py
ln -s /system/lib/sshyp/sshyp.py output/haikutemp/bin/sshyp
cp -r share/doc/sshyp/. output/haikutemp/documentation/packages/sshyp/
cp -r share/licenses/sshyp/. output/haikutemp/documentation/packages/sshyp/
cp extra/sshyp-completion.bash output/haikutemp/data/bash-completion/completions/sshyp
cp extra/manpage output/haikutemp/documentation/man/man1/sshyp.1
gzip output/haikutemp/documentation/man/man1/sshyp.1
cd output/haikutemp
package create -b sshyp-"$version"-"$revision"_all.hpkg
package add sshyp-"$version"-"$revision"_all.hpkg bin documentation
package add sshyp-"$version"-"$revision"_all.hpkg bin lib documentation data
cd ../..
mv packages/haikutemp/sshyp-"$version"-"$revision"_all.hpkg packages/
rm -rf packages/haikutemp
echo -e "\nHaiku packaging complete.\n"
fi
mv output/haikutemp/sshyp-"$version"-"$revision"_all.hpkg output/
rm -rf output/haikutemp
echo -e "\nHaiku packaging complete\n"
} &&
if [ "$distro" == "2" ] || [ "$distro" == "9" ]; then
echo -e '\nPackaging for Debian...\n'
mkdir -p packages/debiantemp/sshyp_"$version"-"$revision"_all/{DEBIAN,usr/share/man/man1}
_create_deb() {
echo -e '\npackaging for Debian/Ubuntu...\n'
mkdir -p output/debiantemp/sshyp_"$version"-"$revision"_all/{DEBIAN,usr/{lib/sshyp,bin,share/{bash-completion/completions,man/man1}}}
echo "Package: sshyp
Version: $version
Section: utils
@@ -81,23 +129,25 @@ Architecture: all
Maintainer: Randall Winkhart <idgr at tutanota dot com>
Description: A light-weight, self-hosted, synchronized password manager
Depends: python3, gnupg, openssh-client, xclip, wl-clipboard
Suggests: bash-completion
Priority: optional
Installed-Size: 185
" > packages/debiantemp/sshyp_"$version"-"$revision"_all/DEBIAN/control
cp -r bin packages/debiantemp/sshyp_"$version"-"$revision"_all/usr/
ln -s /usr/bin/sshyp.py packages/debiantemp/sshyp_"$version"-"$revision"_all/usr/bin/sshyp
cp -r share packages/debiantemp/sshyp_"$version"-"$revision"_all/usr/
cp extra/manpage packages/debiantemp/sshyp_"$version"-"$revision"_all/usr/share/man/man1/sshyp.1
gzip packages/debiantemp/sshyp_"$version"-"$revision"_all/usr/share/man/man1/sshyp.1
dpkg-deb --build --root-owner-group packages/debiantemp/sshyp_"$version"-"$revision"_all/
mv packages/debiantemp/sshyp_"$version"-"$revision"_all.deb packages/
rm -rf packages/debiantemp
echo -e "\nDebian packaging complete.\n"
fi
Installed-Size: 14584
" > output/debiantemp/sshyp_"$version"-"$revision"_all/DEBIAN/control
cp -r lib/. output/debiantemp/sshyp_"$version"-"$revision"_all/usr/lib/sshyp/
ln -s /usr/lib/sshyp/sshyp.py output/debiantemp/sshyp_"$version"-"$revision"_all/usr/bin/sshyp
cp -r share output/debiantemp/sshyp_"$version"-"$revision"_all/usr/
cp extra/sshyp-completion.bash output/debiantemp/sshyp_"$version"-"$revision"_all/usr/share/bash-completion/completions/sshyp
cp extra/manpage output/debiantemp/sshyp_"$version"-"$revision"_all/usr/share/man/man1/sshyp.1
gzip output/debiantemp/sshyp_"$version"-"$revision"_all/usr/share/man/man1/sshyp.1
dpkg-deb --build --root-owner-group output/debiantemp/sshyp_"$version"-"$revision"_all/
mv output/debiantemp/sshyp_"$version"-"$revision"_all.deb output/
rm -rf output/debiantemp
echo -e "\nDebian/Ubuntu packaging complete\n"
} &&
if [ "$distro" == "5" ] || [ "$distro" == "9" ]; then
echo -e '\nPackaging for Termux...\n'
mkdir -p packages/termuxtemp/sshyp_"$version"-"$revision"_all_termux/{data/data/com.termux/files/usr/share/man/man1,DEBIAN}
_create_termux() {
echo -e '\npackaging for Termux...\n'
mkdir -p output/termuxtemp/sshyp_"$version"-"$revision"_all_termux/{DEBIAN,data/data/com.termux/files/usr/{lib/sshyp,bin,share/{bash-completion/completions,man/man1}}}
echo "Package: sshyp
Version: $version
Section: utils
@@ -105,78 +155,61 @@ Architecture: all
Maintainer: Randall Winkhart <idgr at tutanota dot com>
Description: A light-weight, self-hosted, synchronized password manager
Depends: python, gnupg, openssh, termux-api, termux-am
Suggests: bash-completion
Priority: optional
Installed-Size: 185
" > packages/termuxtemp/sshyp_"$version"-"$revision"_all_termux/DEBIAN/control
cp -r bin packages/termuxtemp/sshyp_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/
ln -s /data/data/com.termux/files/usr/bin/sshyp.py packages/termuxtemp/sshyp_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/bin/sshyp
cp -r share packages/termuxtemp/sshyp_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/
cp extra/manpage packages/termuxtemp/sshyp_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/share/man/man1/sshyp.1
gzip packages/termuxtemp/sshyp_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/share/man/man1/sshyp.1
dpkg-deb --build --root-owner-group packages/termuxtemp/sshyp_"$version"-"$revision"_all_termux/
mv packages/termuxtemp/sshyp_"$version"-"$revision"_all_termux.deb packages/
rm -rf packages/termuxtemp
echo -e "\nTermux packaging complete.\n"
fi
Installed-Size: 14584
" > output/termuxtemp/sshyp_"$version"-"$revision"_all_termux/DEBIAN/control
cp -r lib/. output/termuxtemp/sshyp_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/lib/sshyp/
ln -s /data/data/com.termux/files/usr/lib/sshyp/sshyp.py output/termuxtemp/sshyp_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/bin/sshyp
cp -r share output/termuxtemp/sshyp_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/
cp extra/sshyp-completion.bash output/termuxtemp/sshyp_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/share/bash-completion/completions/sshyp
cp extra/manpage output/termuxtemp/sshyp_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/share/man/man1/sshyp.1
gzip output/termuxtemp/sshyp_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/share/man/man1/sshyp.1
dpkg-deb --build --root-owner-group output/termuxtemp/sshyp_"$version"-"$revision"_all_termux/
mv output/termuxtemp/sshyp_"$version"-"$revision"_all_termux.deb output/
rm -rf output/termuxtemp
echo -e "\nTermux packaging complete\n"
} &&
if [ "$distro" == "3" ] || [ "$distro" == "4" ] || [ "$distro" == "6" ] || [ "$distro" == "7" ] || [ "$distro" == "8" ] || [ "$distro" == "9" ]; then
echo -e '\nPackaging as generic...\n'
mkdir -p packages/generictemp/usr/share/man/man1
cp -r bin packages/generictemp/usr/
ln -s /usr/bin/sshyp.py packages/generictemp/usr/bin/sshyp
cp -r share packages/generictemp/usr/
cp extra/manpage packages/generictemp/usr/share/man/man1/sshyp.1
gzip packages/generictemp/usr/share/man/man1/sshyp.1
tar -C packages/generictemp -cvJf packages/sshyp-"$version".tar.xz usr/
rm -rf packages/generictemp
sha512="$(sha512sum packages/sshyp-"$version".tar.xz | awk '{print $1;}')"
echo -e "\nsha512 sum:\n$sha512"
echo -e "\nGeneric packaging complete.\n"
fi
if [ "$distro" == "3" ]; then
echo -e '\nPackaging for Fedora...\n'
_create_rpm() {
echo -e '\npackaging for Fedora...\n'
rm -rf ~/rpmbuild
rpmdev-setuptree
cp packages/sshyp-"$version".tar.xz ~/rpmbuild/SOURCES
cp output/sshyp-"$version".tar.xz ~/rpmbuild/SOURCES
echo "Name: sshyp
Version: "$version"
Release: "$revision"
Summary: A light-weight, self-hosted, synchronized password manager
BuildArch: noarch
License: GPLv3
License: GPL-3.0-only
URL: https://github.com/rwinkhart/sshyp
Source0: sshyp-"$version".tar.xz
Requires: python gnupg openssh wl-clipboard
Requires: python gnupg openssh-clients wl-clipboard
Recommends: bash-completion
%description
sshyp is a password-store compatible CLI password manager available for UNIX(-like) systems - its primary goal is to make syncing passwords and notes across devices as easy as possible via CLI.
%install
tar xf %{_sourcedir}/sshyp-"$version".tar.xz -C %{_sourcedir}
cp -r %{_sourcedir}/usr %{buildroot}
%files
/usr/bin/sshyp
/usr/bin/sshyp.py
/usr/bin/sshync.py
/usr/bin/sshypRemote.py
/usr/lib/sshyp/sshyp.py
/usr/lib/sshyp/sshync.py
/usr/share/bash-completion/completions/sshyp
%license /usr/share/licenses/sshyp/license
%doc /usr/share/doc/sshyp/changelog
%doc /usr/share/man/man1/sshyp.1.gz
%doc
/usr/share/doc/sshyp/changelog
/usr/share/man/man1/sshyp.1.gz
" > ~/rpmbuild/SPECS/sshyp.spec
rpmbuild -bb ~/rpmbuild/SPECS/sshyp.spec
mv ~/rpmbuild/RPMS/noarch/* packages/
mv ~/rpmbuild/RPMS/noarch/* output/
rm -rf ~/rpmbuild
echo -e "\nFedora packaging complete.\n"
fi
echo -e "\nFedora packaging complete\n"
} &&
if [ "$distro" == "4" ] || [ "$distro" == "9" ]; then
echo -e '\nPackaging for FreeBSD...\n'
mkdir -p packages/FreeBSDtemp/bin
tar xf packages/sshyp-"$version".tar.xz -C packages/FreeBSDtemp
_create_freebsd_pkg() {
echo -e '\npackaging for FreeBSD...'
mkdir -p output/freebsdtemp/usr/{lib/sshyp,bin,share/{bash-completion/completions,man/man1}}
echo "name: sshyp
version: \""$version"\"
abi = \"FreeBSD:13:*\";
@@ -201,63 +234,56 @@ prefix: /
\"origin\" : \"x11/wl-clipboard\"
},
},
" > packages/FreeBSDtemp/+MANIFEST
echo "/usr/bin/sshync.py
/usr/bin/sshyp
/usr/bin/sshyp.py
/usr/bin/sshypRemote.py
" > output/freebsdtemp/+MANIFEST
echo "/usr/bin/sshyp
/usr/lib/sshyp/sshync.py
/usr/lib/sshyp/sshyp.py
/usr/share/bash-completion/completions/sshyp
/usr/share/doc/sshyp/changelog
/usr/share/licenses/sshyp/license
/usr/share/man/man1/sshyp.1.gz
" > packages/FreeBSDtemp/plist
pkg create -m packages/FreeBSDtemp/ -r packages/FreeBSDtemp/ -p packages/FreeBSDtemp/plist -o packages/
rm -rf packages/FreeBSDtemp
echo -e "\nFreeBSD packaging complete.\n"
fi
" > output/freebsdtemp/plist
cp -r lib/. output/freebsdtemp/usr/lib/sshyp/
ln -s /usr/lib/sshyp/sshyp.py output/freebsdtemp/usr/bin/sshyp
cp -r share output/freebsdtemp/usr/
cp extra/sshyp-completion.bash output/freebsdtemp/usr/share/bash-completion/completions/sshyp
cp extra/manpage output/freebsdtemp/usr/share/man/man1/sshyp.1
gzip output/freebsdtemp/usr/share/man/man1/sshyp.1
pkg create -m output/freebsdtemp/ -r output/freebsdtemp/ -p output/freebsdtemp/plist -o output/
rm -rf output/freebsdtemp
echo -e "\nFreeBSD packaging complete\n"
} &&
if [ "$distro" == "7" ] || [ "$distro" == "9" ]; then
echo -e '\nGenerating PKGBUILD...'
echo "# Maintainer: Randall Winkhart <idgr at tutanota dot com>
pkgname=sshyp
pkgver="$version"
pkgrel="$revision"
pkgdesc='A light-weight, self-hosted, synchronized password manager'
url='https://github.com/rwinkhart/sshyp'
arch=('any')
license=('GPL3')
depends=(python gnupg openssh xclip wl-clipboard)
source=(\""$source"\")
sha512sums=('"$sha512"')
package() {
tar xf sshyp-"\"\$pkgver\"".tar.xz -C "\"\${pkgdir}\""
}
" > packages/PKGBUILD
echo -e "\nPKGBUILD generated.\n"
fi
if [ "$distro" == "8" ] || [ "$distro" == "9" ]; then
echo -e '\nGenerating APKBUILD...'
echo "# Maintainer: Randall Winkhart <idgr@tutanota.com>
pkgname=sshyp
pkgver="$version"
pkgrel="$((revision-1))"
pkgdesc='A light-weight, self-hosted, synchronized password manager'
options=!check
url='https://github.com/rwinkhart/sshyp'
arch='noarch'
license='GPL-3.0-or-later'
depends='python3 gnupg openssh xclip wl-clipboard'
source=\""$source"\"
package() {
mkdir -p "\"\$pkgdir\""
cp -r "\"\$srcdir/usr/"\" "\"\$pkgdir\""
}
sha512sums=\"
"$sha512' 'sshyp-\"\$pkgver\".tar.xz"
\"
" > packages/APKBUILD
echo -e "\nAPKBUILD generated.\n"
if [ "$1" == "generic" ]; then # build scripts
_create_generic
elif [ "$1" == "pkgbuild" ]; then
_create_generic
_create_pkgbuild
elif [ "$1" == "apkbuild" ]; then
_create_generic
_create_apkbuild
elif [ "$1" == "haiku" ]; then # distribution packages
_create_hpkg
elif [ "$1" == "debian" ]; then
_create_deb
elif [ "$1" == "termux" ]; then
_create_termux
elif [ "$1" == "fedora" ]; then
_create_generic
_create_rpm
elif [ "$1" == "freebsd" ]; then
_create_freebsd_pkg
elif [ "$1" == "buildable-arch" ]; then
_create_generic
_create_pkgbuild
_create_apkbuild
if [[ $(pacman -Q dpkg) == "dpkg"* ]]; then
_create_deb
_create_termux
fi
if [[ "$(pacman -Q freebsd-pkg)" == "freebsd-pkg"* ]]; then
_create_freebsd_pkg
fi
else
echo -e '\nusage: package.sh [target] <revision>\n\ntargets:\n mainline: pkgbuild apkbuild haiku fedora debian\n experimental: freebsd termux\n other: buildable-arch\n'
fi
Executable → Regular
+38 -11
View File
@@ -1,21 +1,48 @@
sshyp v1.1.2
sshyp v1.3.0
the sheecrets update - patch two
the serious shepherd update
this is a general polish/bugfix release
this release ties up many of sshyp's loose ends where there was
room for major performance, compatibility, and security improvements
compatibility-breaking changes:
- due to a near full re-write of the syncing functionality, all clients and servers
must be updated to this release (v1.3.0 is not backwards compatible with any prior release)
- it is recommended to either delete the contents of ~/.config/sshyp/deleted (on the server-side)
or sync all of your clients before updating
^ old entries in this folder will throw errors with v1.3.0
user-facing features:
- packaging support for Alpine Linux/postmarketOS
- the rename() function is no longer unnecessarily verbose
- handled exception for when the user attempts to copy an entry field that does not exist
- none - all changes were under-the-hood - the user experience should be
exactly the same as v1.2.0 - just faster, less buggy, and more secure
fixes/optimizations:
major fixes/optimizations:
- replaced shebang with "#!/usr/bin/env python3" for improved compatibility with various systems
^ does not affect Haiku packaging
- fixed the generic package not actually being compressed
- reduced lines of code used for cross-device entry deletion
- a near full re-write of the syncing functionality
^ all syncing logic has been moved into sshync.py (from sshyp.py and sshypRemote.py)
^ in my setup, a dry, local "sshyp sync" went from 2.00+ seconds (v1.2.0) to 0.36 seconds (v1.3.0)
^ the performance improvements are even greater when syncing from outside your local network
- the following character sequences will no longer break the syncing logic: "@", "^&*", and "*&^"
^ ASCII separator characters 29-31 are now used, instead
- os.system has been replaced with subprocess.run in all cases, shell=True is no longer used with subprocess.run
^ this protects against shell escape attacks and potentially makes sshyp more compatible with some environments
- replaced shell commands with python built-in library functions where applicable
^ this brings speed and compatibility improvements
- sshyp should no longer incorrectly assume an X11 environment when Wayland is in use
^ this fixes clipboard support in some Wayland environments, such as Sway (Plasma/Gnome/Phosh were unaffected)
- sshyp now uses the default pinentry on Haiku thanks to haikuports/haikuports#7457
^ this brings the Haiku port in-line with the other sshyp packages in terms of security
- "python3" is now called over ssh, rather than "python"
^ some environments do not have a "python" symlink, or it links to "python2" - changing this increases compatibility
- fixed an issue from v1.2.0 where renaming threw an error if not in offline mode
other notable changes:
- quick-unlock password input is now hidden while the user is typing
^ user input is now invisible to prevent snooping
- lots of smaller optimizations not listed here
<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
Executable → Regular
+3 -4
View File
@@ -2,9 +2,8 @@
Copyright (C) 2022 Randall Winkhart idgr@tutanota.com
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.
it under the terms of version 3 (only) of the GNU General Public License
as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -12,4 +11,4 @@
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
along with this program. If not, see <http://www.gnu.org/licenses/>.