Compare commits

...
Author SHA1 Message Date
RandyTheSilly ffafbd2b91 Improved entry exists checks when reading entry 2022-05-23 00:28:44 -04:00
RandyTheSilly d7bf7da177 Added exception for renaming something that doesn't exist 2022-05-23 00:00:45 -04:00
RandyTheSilly 13d466bb21 Reduced lines used for KeyboardInterrupt exception 2022-05-22 23:43:34 -04:00
RandyTheSilly 7aa9ea63e2 Addressed line-length warnings 2022-05-22 23:18:33 -04:00
RandyTheSilly 208bd8707b Error messages are now more consistent and informative 2022-05-22 22:41:05 -04:00
RandyTheSilly fb398333a6 Added error messages for trying to read entries that don't exist 2022-05-22 22:28:17 -04:00
RandyTheSilly b369f9a530 Added script for optimizing and re-encrypting entries 2022-05-22 21:39:51 -04:00
RandyTheSilly 747f83819d Change version to be more in-line with semantic versioning 2022-05-22 20:27:36 -04:00
RandyTheSilly 2f1e693991 Add conversion script for converting to the sshyp v1.0.0 entry format 2022-05-22 20:12:58 -04:00
RandyTheSilly dd811cbe35 Fix for reading an entry from the entry list using the old entry readout 2022-05-20 03:46:23 -04:00
RandyTheSilly 87dee4f197 Move documentation for Haiku packaging 2022-05-20 03:29:20 -04:00
RandyTheSilly bf86d72472 Fixes needed for Haiku packaging to succeed 2022-05-20 03:18:45 -04:00
RandyTheSilly a3722e3c29 Correct typo 2022-05-20 03:02:42 -04:00
RandyTheSilly 1f4d5b0591 Added Haiku support to packaging script 2022-05-20 02:59:18 -04:00
RandyTheSilly e21faa8bbb Update documentation 2022-05-19 22:46:35 -04:00
RandyTheSilly 309a471e77 Shorten /usr/bin to /bin for Haiku server compatibility 2022-05-19 17:10:51 -04:00
RandyTheSilly 968a7322f8 Changed shade of gray used in entry list to be more neutral 2022-05-19 16:55:20 -04:00
RandyTheSilly 75e152ebfe Changed shade of gray used in entry list to be more neutral 2022-05-19 16:50:22 -04:00
RandyTheSilly 15204fb0e9 Improved entry list readability in terminals with white backgrounds 2022-05-19 16:18:58 -04:00
RandyTheSilly 5977367f28 Replace remaining sftp commands with scp -s 2022-05-19 15:37:46 -04:00
RandyTheSilly 96eb80226f sshyp server no longer makes copies of libraries in ~/ 2022-05-19 15:13:02 -04:00
RandyTheSilly 9ede33d3d8 Update README.md 2022-05-17 03:13:45 -04:00
RandyTheSilly 4093c36d48 Update README.md 2022-05-17 03:12:25 -04:00
RandyTheSilly 947754f87c Update README.md 2022-05-17 03:09:31 -04:00
RandyTheSilly 84bfefb7b1 Fixed skipped lines and extra blank lines in the notes field of the entry reader 2022-05-16 12:57:35 -04:00
RandyTheSilly 7ac5cd723e New entry format (for GNU pass compatibility) 2022-05-16 03:24:18 -04:00
RandyTheSilly 68aa5eee04 Overhauled entry readout (when reading/previewing) 2022-05-16 01:43:02 -04:00
RandyTheSilly f869e3b45d Reduced lines used for clipboard clearing 2022-05-16 00:23:03 -04:00
RandyTheSilly 551f1e8f50 Reduce clipboard timeout to 30 seconds 2022-05-16 00:16:45 -04:00
RandyTheSilly 1687d9e1e2 Removed unused import statement 2022-05-15 23:53:52 -04:00
RandyTheSilly 930595ecad Clipboard now clears after 45 seconds 2022-05-15 23:53:01 -04:00
RandyTheSilly 2d00da0e18 Implemented link to /tmp for Haiku 2022-05-15 18:13:07 -04:00
RandyTheSilly c84d52fb8d Restored support for openssh versions prior to 9.0 2022-05-15 18:03:38 -04:00
RandyTheSilly 88fd9e77a9 Move gpg variable declaration to happen before configuration (fixes gpg setup on Haiku) 2022-05-15 17:53:56 -04:00
RandyTheSilly 4258ad85ab Added ssh support for Haiku 2022-05-15 17:42:14 -04:00
RandyTheSilly 337e68d088 Added gpg support for Haiku 2022-05-15 17:32:33 -04:00
RandyTheSilly fb8c5be284 Added clipboard support for Haiku 2022-05-15 17:07:26 -04:00
RandyTheSilly 0293d273a7 Corrected typo causing maximum password length to be 800, rather than 840 2022-05-15 03:58:40 -04:00
RandyTheSilly 40a43ed86d Added generated password limit of 840 characters (to avoid recursion limit) 2022-05-15 03:57:03 -04:00
RandyTheSilly 18c78743ca Addressed line-length warnings 2022-05-15 03:47:47 -04:00
RandyTheSilly 2f5080fec0 Fixed user being served complexity prompts multiple times when password generation is attempted more than once 2022-05-15 03:42:16 -04:00
RandyTheSilly 99e0d5f666 Added EOF return code 2022-04-20 21:00:15 -04:00
RandyTheSilly be19e271c5 Added return codes 2022-04-20 20:57:59 -04:00
RandyTheSilly 20d2de338f Silenced gpg output 2022-04-19 19:10:38 -04:00
RandyTheSilly e830d8486b Decryption is no longer necessary for previews when creating/editing entries 2022-04-19 18:57:27 -04:00
RandyTheSilly 5fc74ac2b3 Improved minimum quality of generated passwords 2022-04-19 16:43:30 -04:00
RandyTheSilly 24acd1cb87 Old files are now deleted from the server when renaming 2022-04-19 15:48:27 -04:00
RandyTheSilly 0079934354 Renamed folders are now created on the server 2022-04-19 15:27:06 -04:00
RandyTheSilly 399e291902 Optimize rename function and allow for renaming folders 2022-04-19 15:18:34 -04:00
RandyTheSilly 66a026e327 Added new, colored syncing operation messages 2022-04-19 01:55:12 -04:00
RandyTheSilly b5f4e776e5 Removed file extension from sshync messages 2022-04-19 01:18:44 -04:00
RandyTheSilly 0056f60af1 Moved password generator to new function 2022-04-19 01:14:20 -04:00
RandyTheSilly 41aa093685 Updated roadmap 2022-04-19 00:54:32 -04:00
RandyTheSilly 30034be36f Updated help text 2022-04-19 00:43:50 -04:00
RandyTheSilly a43530d34f Corrected typo in manpage 2022-04-19 00:20:18 -04:00
RandyTheSilly 7d14c49490 Updated manpage 2022-04-19 00:10:59 -04:00
RandyTheSilly 462ba05833 Fix syncing with multi-word entries 2022-04-18 23:47:59 -04:00
RandyTheSilly e89bf06d9a Made entry list monochrome 2022-04-18 22:54:33 -04:00
RandyTheSilly d33d07894f Reverted accidental change from last commit 2022-04-16 19:12:48 -04:00
RandyTheSilly cade83a0f0 Added ability to pass entry names as arguments to most sshyp functions 2022-04-16 19:07:48 -04:00
RandyTheSilly 100edd07ea Addressed line-length warnings 2022-04-09 17:02:35 -04:00
RandyTheSilly d19651819a Use 'if __name__ == "__main__":' convention 2022-04-09 16:52:12 -04:00
RandyTheSilly d951284bcd Switch to scp via sftp for changes in openssh 9.0 2022-04-08 16:16:17 -04:00
RandyTheSilly 3c9b80ec80 Corrected copyright date in help menu 2022-04-08 14:52:49 -04:00
RandyTheSilly 0f3d9854b7 Optimized argument parsing 2022-04-08 14:50:36 -04:00
RandyTheSilly 0c52149c40 Correct misinformation in packaging script 2022-03-16 03:36:59 -04:00
RandyTheSilly f838af0b4e Updated license 2022-03-16 03:25:47 -04:00
RandyTheSilly 72f126d21d Removed unnecessary blank line in entry list 2022-03-02 08:14:06 -05:00
RandyTheSilly 2385f669f8 Alphabetized import statements 2022-03-02 05:45:01 -05:00
RandyTheSilly b8896a6b58 Abbreviated Python declarations 2022-03-02 05:36:21 -05:00
RandyTheSilly 51ec2143e3 Properly handle empty root directory 2022-03-02 04:42:42 -05:00
RandyTheSilly 4c1989287a Added exception handling for entry list generation with an empty directory 2022-03-02 04:28:05 -05:00
RandyTheSilly daba737ca6 Folders are now sorted alphabetically in the entry list 2022-03-02 04:20:30 -05:00
RandyTheSilly 8cac27c6f6 Increased text wrapping safety tolerance 2022-03-02 03:53:36 -05:00
RandyTheSilly 19d37dfdb6 Entry list is now generated alphabetically 2022-03-02 03:50:20 -05:00
RandyTheSilly 87a56a80bd Switched to horizontal view for entry list 2022-03-02 03:30:33 -05:00
RandyTheSilly 5aaa284cbf Initial implementation of new entry list 2022-03-02 00:37:55 -05:00
RandyTheSilly a6aa0561b4 Updated roadmap for future releases 2022-02-16 18:24:22 -05:00
RandyTheSilly b08456e873 Updated documentation for release fr4.7 2022-02-16 16:24:11 -05:00
RandyTheSilly ef828b88a0 Added user-facing messages when deleting via sync 2022-02-16 16:10:37 -05:00
RandyTheSilly bde243c644 Continuation of recursive fix 2022-02-16 15:59:11 -05:00
RandyTheSilly 894f406fd2 Fix recursive folder syncing reporting incorrect directories 2022-02-16 15:57:23 -05:00
RandyTheSilly 00b935f1bb Allow for recursive folder syncing 2022-02-16 15:43:33 -05:00
RandyTheSilly 49608cb839 Updated changelog for fr4.6 2022-02-16 13:51:12 -05:00
RandyTheSilly a417478aa0 Fixed critical typo preventing folder syncing from working 2022-02-16 13:46:42 -05:00
RandyTheSilly 344c74b728 Updated changelog for release fr4.5 2022-02-16 13:28:00 -05:00
RandyTheSilly fc0e4bb4a5 Bump version 2022-02-16 13:23:43 -05:00
RandyTheSilly 37867cb673 More renaming 2022-02-16 13:22:10 -05:00
RandyTheSilly 86aa7da038 Renamed file to avoid syntax error 2022-02-16 13:20:24 -05:00
RandyTheSilly 9231257af8 Updated README.md to reflect new folder syncing changes 2022-02-16 12:50:29 -05:00
RandyTheSilly 102f22143f Updated manpage to reflect new folder syncing changes 2022-02-16 12:48:24 -05:00
RandyTheSilly 3dd12b4d6d Updated changelog for release fr4.4 2022-02-16 12:46:52 -05:00
RandyTheSilly a0b5dad073 Automatically remove old device names locally 2022-02-16 12:37:20 -05:00
RandyTheSilly 2ba19f9a39 Initial folder sync implementation 2022-02-16 12:02:45 -05:00
RandyTheSilly 8af71ceb1a More renaming 2022-02-16 09:44:24 -05:00
RandyTheSilly 81c9b35c75 Rename deletion check function 2022-02-16 09:27:22 -05:00
RandyTheSilly eeabc18f18 Fixed typo 2022-02-16 09:23:29 -05:00
RandyTheSilly 73128665fb Rename sshyp-shear.py to sshyp-remote.py for future intentions 2022-02-16 09:15:33 -05:00
RandyTheSilly 607a4709f6 Update README.md 2022-02-16 01:42:08 -05:00
RandyTheSilly c643e861d9 Updated changelog for release fr4.3 2022-02-16 01:39:35 -05:00
RandyTheSilly 23e7e375ae Properly applied fix from last commit 2022-02-16 01:35:48 -05:00
RandyTheSilly b8743d5fe2 Fixed local entry (not folders this time) deletion 2022-02-16 01:33:08 -05:00
RandyTheSilly e95aa85453 Updated changelog for release fr4.2 2022-02-16 01:18:12 -05:00
RandyTheSilly caf130568e Version bump for release fr4.2 2022-02-16 01:15:19 -05:00
RandyTheSilly f65adc17f3 Actually fixed local folder deletion this time 2022-02-16 01:13:35 -05:00
RandyTheSilly fc2f42a1aa Removed extra new line printed after some remote file checks 2022-02-16 01:07:03 -05:00
RandyTheSilly 8403f4af98 Updated changelog for release fr4.1 2022-02-16 00:50:39 -05:00
RandyTheSilly 4b24df0e97 Deletion flags no longer end in 'del' 2022-02-16 00:45:26 -05:00
RandyTheSilly ca007f55ac '.' is no longer an unsupported character 2022-02-16 00:42:16 -05:00
RandyTheSilly 7afcfdb43d Updated manpage to disclose unsupported characters 2022-02-16 00:32:55 -05:00
RandyTheSilly 5178377827 Fixed local folder deletion 2022-02-16 00:23:48 -05:00
11 changed files with 993 additions and 403 deletions
+18 -26
View File
@@ -1,5 +1,9 @@
# sshyp # sshyp
A very simple self-hosted, synchronized password manager. Alternative to GNU pass. A very simple self-hosted, synchronized password manager for UNIX systems (currently Haiku/Linux). Alternative to (and compatible with) pass/password-store.
Compatible with entries created by pass/password-store.
The only password-store compatible CLI password manager available for Haiku.
"sshyp" stands for "sshync passwords", or "ssh sync passwords". "sshyp" stands for "sshync passwords", or "ssh sync passwords".
"sshync" is the custom syncing backend (based on sftp) used by "sshyp". "sshync" is the custom syncing backend (based on sftp) used by "sshyp".
@@ -16,18 +20,18 @@ What sshyp can do:
- securely manage a collection of encrypted passwords and notes via CLI - securely manage a collection of encrypted passwords and notes via CLI
- generate new, secure passwords to the user's choice in length and complexity - generate new, secure passwords to the user's choice in length and complexity
- securely sync said passwords and notes seamlessly between multiple POSIX compliant Unix devices - securely sync said passwords and notes seamlessly between devices
- everything above with entries created by pass/password-store!
- everything above on Haiku, Linux, and Termux (an Android terminal emulator)!
What sshyp will do: What sshyp will likely do:
- everything it already does, but also in a GUI for Linux/Android - (planned v1.1.0) 2FA/MFA management (was previously not on the roadmap, but could be beneficial in reducing phone usage)
- have a distinct retro theme - (planned v1.2.0) everything it already does, but also in a GUI for Linux/Haiku
- receive many usability enhancements
What sshyp definitely won't do: What sshyp definitely won't do:
- 2FA/MFA (for security purposes, this should not be handled by your password manager) - Non-UNIX support, e.g. Windows (I'd be happy to link to third-party ports, if someone were to make them)
- Windows support (I don't use Windows and I have no interest in developing for it)
# Installation # Installation
Arch Linux (all ISAs) Arch Linux (all ISAs)
@@ -42,7 +46,7 @@ cd sshyp
makepkg -si makepkg -si
``` ```
Pre-built packages exist for Arch, Debian, and Termux. These can be downloaded from the releases page. Pre-built packages exist for Haiku, Arch Linux, Debian Linux, and Termux. These can be downloaded from the releases page.
# Building # Building
Since sshyp is written entirely in Python, it doesn't need to be compiled. It does, however, need to be packaged for installation. Since sshyp is written entirely in Python, it doesn't need to be compiled. It does, however, need to be packaged for installation.
@@ -59,7 +63,7 @@ The packaging script has been tested on Arch Linux with dpkg as a dependency for
The AUR version and the packages attatched to the release tags were already packaged using this script. The AUR version and the packages attatched to the release tags were already packaged using this script.
Currently, the script can create packages for Arch (PKGBUILD), Debian, Termux, and generic. Packaging for other distributions coming soon. Currently, the script can create packages for Haiku, Arch Linux (PKGBUILD), Debian Linux, Termux, and generic. Packaging for other systems coming soon.
# Usage # Usage
Upon initial installation (on both the server and client devices), be sure to run: Upon initial installation (on both the server and client devices), be sure to run:
@@ -69,7 +73,7 @@ sshyp tweak
``` ```
This command will allow you to configure the settings necessary for sshyp to function. This command will allow you to configure the settings necessary for sshyp to function.
As of right now, sshyp is rapidly changing, and as such, it is a good idea to run "sshyp tweak" after each update. To ensure configuration compatibility, it is a good idea to run 'sshyp tweak' after each major update.
Please note that decrypting and reading entries is disabled on server devices for security reasons. Only devices configured as clients can use the gpg key to decrypt entries. Please note that decrypting and reading entries is disabled on server devices for security reasons. Only devices configured as clients can use the gpg key to decrypt entries.
@@ -79,7 +83,7 @@ All available options can be found with:
sshyp --help sshyp --help
``` ```
Or alternatively, in the new manpage: Or alternatively, in the manpage:
``` ```
man sshyp man sshyp
@@ -88,22 +92,10 @@ man sshyp
# Roadmap # Roadmap
Short-term Goals: Short-term Goals:
- create new file list w/color and w/o file extensions - 2FA/MFA management
- overhaul argument system
- fix lots of bugs!
- make lots of optimizations!
Medium-term Goals:
- create minimal GUI apps (Linux x86_64, Linux aarch64) - create minimal GUI apps (Linux x86_64, Linux aarch64)
- auto-completion on tab for CLI version - various optimizations/bug fixes
Long-term Goals: Long-term Goals:
- seize the thrones, shear the humans - seize the thrones, shear the humans
# Known Issues
Currently, files deleted from a client or server may re-appear if another client that had the same file reconnects and later re-uploads it to the server. This will be addressed.
Currently, if a client is missing folders that exist on the server, the contents of the missing folders will fail to download. A temporary workaround is to manually create the folders on the client. This will be addressed.
+22 -24
View File
@@ -1,13 +1,11 @@
#!/usr/bin/python3 #!/bin/python3
# sshync 2022.01.17.unreleased13
# external modules # external modules
from os import path, system, walk from os import path, system, walk
from os.path import getmtime, join, expanduser from os.path import expanduser, getmtime, join
from sys import exit as s_exit
from pathlib import Path from pathlib import Path
from sys import exit as s_exit
# utility functions # utility functions
@@ -29,13 +27,13 @@ def get_titles_mods(_directory, _destination, _user_data):
open(expanduser('~/.config/sshync/database'), 'a').write(str(_mod) + '\n') open(expanduser('~/.config/sshync/database'), 'a').write(str(_mod) + '\n')
# remote fetching # remote fetching
if _destination == 'r': if _destination == 'r':
system(f"ssh -i '{_user_data[5]}' -p {_user_data[2]} {_user_data[0]}@{_user_data[1]} \"python -c 'import sshync" system(f"ssh -i '{_user_data[5]}' -p {_user_data[2]} {_user_data[0]}@{_user_data[1]} \"cd /bin; python -c 'impo"
f"; sshync.get_titles_mods(\"'\"{_user_data[4]}\"'\", \"'\"l\"'\", \"'\"{_user_data}\"'\")'\"") f"rt sshync; sshync.get_titles_mods(\"'\"{_user_data[4]}\"'\", \"'\"l\"'\", \"'\"{_user_data}\"'\")'\"")
system(f"sftp -i '{_user_data[5]}' -P {_user_data[2]} {_user_data[0]}@{_user_data[1]}:" system(f"scp -pqs -P {_user_data[2]} -i '{_user_data[5]}' {_user_data[0]}@{_user_data[1]}:"
f"'{expanduser(f'/home/{_user_data[0]}/.config/sshync/database')}' '{expanduser('~/.config/sshync/')}'") f"'/home/{_user_data[0]}/.config/sshync/database' '{expanduser('~/.config/sshync/')}'")
_titles, _sep, _mods = ' '.join(open(expanduser('~/.config/sshync/database')).readlines()).replace('\n', '')\ _titles, _sep, _mods = '*&^'.join(open(expanduser('~/.config/sshync/database')).readlines()).replace('\n', '')\
.partition('^&*') .partition('^&*')
_title_list, _mod_list = _titles.split(' ')[:-1], _mods.split(' ')[1:] _title_list, _mod_list = _titles.split('*&^')[:-1], _mods.split('*&^')[1:]
return _title_list, _mod_list return _title_list, _mod_list
@@ -65,7 +63,7 @@ def get_profile(_profile_dir):
except (FileNotFoundError, IndexError): except (FileNotFoundError, IndexError):
print('\nEither the profile does not exist, or it is corrupted.\n') print('\nEither the profile does not exist, or it is corrupted.\n')
_profile_data = None _profile_data = None
s_exit() s_exit(1)
# extract data from profile # extract data from profile
_user = _profile_data[0].replace('\n', '') _user = _profile_data[0].replace('\n', '')
_ip = _profile_data[1].replace('\n', '') _ip = _profile_data[1].replace('\n', '')
@@ -87,19 +85,19 @@ def run_profile(_profile_dir):
if _title in _index_r[0]: if _title in _index_r[0]:
# compare mod times and sync # compare mod times and sync
if int(_index_l[1][_i]) > int(_index_r[1][_i]): if int(_index_l[1][_i]) > int(_index_r[1][_i]):
print(f"[{_title}] Local is newer, uploading...") print(f"\u001b[38;5;4m{_title[:-4]}\u001b[0m is newer locally, uploading...")
system(f"sftp -p -q -i '{_user_data[5]}' -P {_user_data[2]} {_user_data[0]}@{_user_data[1]}:" system(f"scp -pqs -P {_user_data[2]} -i '{_user_data[5]}' '{_user_data[3]}{_title}' "
f"{_user_data[4]}{'/'.join(_title.split('/')[:-1]) + '/'} <<< $'put {_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]): elif int(_index_l[1][_i]) < int(_index_r[1][_i]):
print(f"[{_title}] Remote is newer, downloading...") print(f"\u001b[38;5;2m{_title[:-4]}\u001b[0m is newer remotely, downloading...")
system(f"sftp -p -q -i '{_user_data[5]}' -P {_user_data[2]} {_user_data[0]}@{_user_data[1]}:" 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]) + '/'}") f"{_user_data[4]}{_title}' '{_user_data[3]}{'/'.join(_title.split('/')[:-1]) + '/'}'")
else: else:
print(f"[{_title}] Not on remote server, uploading...") print(f"\u001b[38;5;4m{_title[:-4]}\u001b[0m is not on remote server, uploading...")
system(f"sftp -p -q -i '{_user_data[5]}' -P {_user_data[2]} {_user_data[0]}@{_user_data[1]}:{_user_data[4]}" system(f"scp -pqs -P {_user_data[2]} -i '{_user_data[5]}' '{_user_data[3]}{_title}' "
f"{'/'.join(_title.split('/')[:-1]) + '/'} <<< $'put {_user_data[3]}{_title}'") f"'{_user_data[0]}@{_user_data[1]}:{_user_data[4]}{'/'.join(_title.split('/')[:-1]) + '/'}'")
for _title in _index_r[0]: for _title in _index_r[0]:
if _title not in _index_l[0]: if _title not in _index_l[0]:
print(f"[{_title}] Not in local directory, downloading...") print(f"\u001b[38;5;2m{_title[:-4]}\u001b[0m is not in local directory, downloading...")
system(f"sftp -p -q -i '{_user_data[5]}' -P {_user_data[2]} {_user_data[0]}@{_user_data[1]}:" 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]) + '/'}") f"{_user_data[4]}{_title}' '{_user_data[3]}{'/'.join(_title.split('/')[:-1]) + '/'}'")
+444 -265
View File
@@ -1,18 +1,90 @@
#!/usr/bin/python3 #!/bin/python3
# external modules # external modules
from os import environ, listdir, path, remove, system from os import environ, listdir, path, remove, system, uname, walk
from shutil import copy, move, rmtree
from sys import argv, exit as s_exit
from pathlib import Path from pathlib import Path
import random import random
from shutil import get_terminal_size, move, rmtree
import sshync import sshync
import string import string
from subprocess import Popen
from sys import argv, exit as s_exit
from textwrap import fill
# utility functions # utility functions
def entry_list_gen(): # generates and prints a list of all folders and entries
print('\n\u001b[38;5;0;48;5;15msshyp entries:\u001b[0m\n')
_entry_list, _color_alternator = [], 1
for _entry in sorted(listdir(path.expanduser('~/.password-pasture'))):
if Path(path.expanduser(f"~/.password-pasture/{_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(path.expanduser('~/.password-pasture')):
for _dir in sorted(_directories):
_inner_dir = f"{_root.replace(path.expanduser('~/.password-pasture'), '')}/{_dir}"
print(f"\n\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(path.expanduser(f"~/.password-pasture{_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;208m-empty directory!-\u001b[0m\n')
def entry_reader(_decrypted_entry):
_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;0;48;5;15mUsername:\u001b[0m\n{_entry_lines[1]}")
elif _num == 1 and _entry_lines[0] != '\n':
print(f"\u001b[38;5;0;48;5;15mPassword:\u001b[0m\n{_entry_lines[0]}")
elif _num == 2 and _entry_lines[2] != '\n':
print(f"\u001b[38;5;0;48;5;15mURL:\u001b[0m\n{_entry_lines[_num]}")
elif _num >= 3 and _entry_lines[_num] != '\n' and _notes_flag != 1:
_notes_flag = 1
print('\u001b[38;5;0;48;5;15mNotes:\u001b[0m\n' + _entry_lines[_num].replace('\n', ''))
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;0;48;5;15mPassword:\u001b[0m\n{_entry_lines[0]}")
def replace_line(file_name, line_num, text): # replaces text in a given line with different text def replace_line(file_name, line_num, text): # replaces text in a given line with different text
_lines = open(file_name, 'r').readlines() _lines = open(file_name, 'r').readlines()
_lines[line_num] = text _lines[line_num] = text
@@ -28,8 +100,31 @@ def shm_gen(): # generates a random temporary folder for security and returns r
return _shm_folder_gen, _shm_entry_gen return _shm_folder_gen, _shm_entry_gen
def pass_gen():
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(random.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
_length = int(input('How many characters should be in the password? '))
if _length > 840:
_length = 840
print('\n\u001b[38;5;208mPassword length has been limited to the maximum of 840 characters.\u001b[0m\n')
_complexity = str(input('Should the password be simple (for compatibility) or complex (for security)? (s/C) '))
_gen = _pass_gen_function(_complexity, _length)
return _gen
def encrypt(_shm_folder, _shm_entry, _location): def encrypt(_shm_folder, _shm_entry, _location):
system(f"gpg -r {str(gpg_id)} -e '{tmp_dir}{_shm_folder}/{_shm_entry}'") system(f"{gpg} -qr {str(gpg_id)} -e '{tmp_dir}{_shm_folder}/{_shm_entry}'")
move(f"{tmp_dir}{_shm_folder}/{_shm_entry}.gpg", f"{directory}{_location}.gpg") move(f"{tmp_dir}{_shm_folder}/{_shm_entry}.gpg", f"{directory}{_location}.gpg")
rmtree(f"{tmp_dir}{_shm_folder}") rmtree(f"{tmp_dir}{_shm_folder}")
@@ -37,10 +132,10 @@ def encrypt(_shm_folder, _shm_entry, _location):
def edit_note(_shm_folder, _shm_entry): def edit_note(_shm_folder, _shm_entry):
lines = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}").readlines() lines = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}").readlines()
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w').writelines(lines[0:3]) open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w').writelines(lines[0:3])
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'w').writelines(lines[4:-2]) open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'w').writelines(lines[3:])
system(f"nano {tmp_dir}{_shm_folder}/{_shm_entry}-n") system(f"nano {tmp_dir}{_shm_folder}/{_shm_entry}-n")
edit_notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n").read() edit_notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n").read()
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'a').write('\n' + edit_notes + '\n\n') open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'a').write(edit_notes)
# argument-specific functions # argument-specific functions
@@ -51,7 +146,9 @@ def tweak(): # runs configuration wizard
Path(path.expanduser('~/.password-pasture')).mkdir(0o700, parents=True, exist_ok=True) Path(path.expanduser('~/.password-pasture')).mkdir(0o700, parents=True, exist_ok=True)
Path(path.expanduser('~/.config/sshyp/devices')).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 not Path(f"{path.expanduser('~/.config/sshyp/tmp')}").exists():
if Path("/data/data/com.termux").exists(): if uname()[0] == 'Haiku':
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')}") system(f"ln -s '/data/data/com.termux/files/usr/tmp' {path.expanduser('~/.config/sshyp/tmp')}")
else: else:
system(f"ln -s /dev/shm {path.expanduser('~/.config/sshyp/tmp')}") system(f"ln -s /dev/shm {path.expanduser('~/.config/sshyp/tmp')}")
@@ -60,10 +157,8 @@ def tweak(): # runs configuration wizard
if _device_type.lower() == 's': if _device_type.lower() == 's':
open(path.expanduser('~/.config/sshyp/sshyp-device'), 'w').write(_device_type) open(path.expanduser('~/.config/sshyp/sshyp-device'), 'w').write(_device_type)
Path(path.expanduser('~/.config/sshyp/deleted')).mkdir(0o700, parents=True, exist_ok=True) Path(path.expanduser('~/.config/sshyp/deleted')).mkdir(0o700, parents=True, exist_ok=True)
copy('/usr/bin/sshync.py', path.expanduser('~/'))
copy('/usr/bin/sshyp-shear.py', path.expanduser('~/shear.py'))
print('\nMake sure the ssh service is running and properly configured.\n\nConfiguration complete!\n') print('\nMake sure the ssh service is running and properly configured.\n\nConfiguration complete!\n')
s_exit() s_exit(0)
else: else:
# device type configuration # device type configuration
_device_type = 'c' # ensure device type flag is properly set _device_type = 'c' # ensure device type flag is properly set
@@ -74,13 +169,13 @@ def tweak(): # runs configuration wizard
'willing to use? (y/N)') 'willing to use? (y/N)')
if _gpg_id.lower() != 'y': if _gpg_id.lower() != 'y':
print('\nA unique gpg key has been generated for you.') print('\nA unique gpg key has been generated for you.')
system('gpg --full-generate-key') system(f"{gpg} --full-generate-key")
_gpg_id = str(input('\nPlease input the ID of your gpg key: ')) _gpg_id = str(input('\nPlease input the ID of your gpg key: '))
open(path.expanduser('~/.config/sshyp/sshyp-gpg'), 'w').write(_gpg_id + '\n') open(path.expanduser('~/.config/sshyp/sshyp-gpg'), 'w').write(_gpg_id + '\n')
# lock file generation # lock file generation
open(path.expanduser('~/.config/sshyp/lock'), 'w') open(path.expanduser('~/.config/sshyp/lock'), 'w')
system(f"gpg -r {str(_gpg_id)} -e {path.expanduser('~/.config/sshyp/lock')}") system(f"{gpg} -qr {str(_gpg_id)} -e {path.expanduser('~/.config/sshyp/lock')}")
remove(f"{path.expanduser('~/.config/sshyp')}/lock") remove(f"{path.expanduser('~/.config/sshyp')}/lock")
# ssh key configuration # ssh key configuration
@@ -88,6 +183,8 @@ def tweak(): # runs configuration wizard
'\n\nSync support requires a unique ssh key. Would you like to have this automatically ' '\n\nSync support requires a unique ssh key. Would you like to have this automatically '
'generated? (Y/n) ')) 'generated? (Y/n) '))
if _ssh_gen.lower() != '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') system('ssh-keygen -t ed25519 -f ~/.ssh/sshyp')
else: else:
print(f"\nEnsure that the key file you are using is located at {path.expanduser('~/.ssh/sshyp')}") print(f"\nEnsure that the key file you are using is located at {path.expanduser('~/.ssh/sshyp')}")
@@ -107,6 +204,8 @@ def tweak(): # runs configuration wizard
path.expanduser('~/.ssh/sshyp'), _ip, _port, _username_ssh) path.expanduser('~/.ssh/sshyp'), _ip, _port, _username_ssh)
# device name configuration # 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('\nIMPORTANT: This name MUST be unique amongst your client devices. ' print('\nIMPORTANT: This name MUST be unique amongst your client devices. '
'\nThis is used to keep track of which devices have up-to-date databases.\n') '\nThis is used to keep track of which devices have up-to-date databases.\n')
_client_device_name = str(input('What would you like to name this device? ')) _client_device_name = str(input('What would you like to name this device? '))
@@ -119,11 +218,11 @@ def tweak(): # runs configuration wizard
def print_info(): # prints help text based on argument def print_info(): # prints help text based on argument
if argument == 'help' or argument == '--help' or argument == '-h': if argument == 'help' or argument == '--help' or argument == '-h':
print('\nsshyp Copyright (C) 2021 Randall Winkhart') print('\n\u001b[38;5;0;48;5;15msshyp 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 " 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.") "comes with ABSOLUTELY NO WARRANTY;\ntype `sshyp license' for details.")
print('\nUSAGE: sshyp [/<entry name>] [OPTION] [FLAG]\n') print('\n\u001b[38;5;15;48;5;238mUSAGE: sshyp [OPTION [FLAG] [<entry name>]] | [/<entry name>]\u001b[0m\n')
print('Options: ') print('\u001b[38;5;0;48;5;15mOptions:\u001b[0m')
print('help/--help/-h bring up this menu') print('help/--help/-h bring up this menu')
print('version/-v display sshyp version info') print('version/-v display sshyp version info')
print('tweak configure sshyp') print('tweak configure sshyp')
@@ -133,35 +232,40 @@ def print_info(): # prints help text based on argument
print('copy copy details of an entry to your clipboard') print('copy copy details of an entry to your clipboard')
print('shear/-rm delete an existing entry') print('shear/-rm delete an existing entry')
print('sync/-s manually sync the entry directory via sshync\n') print('sync/-s manually sync the entry directory via sshync\n')
print('Flags:') print('\u001b[38;5;0;48;5;15mFlags:\u001b[0m')
print('add:') print('\u001b[38;5;15;48;5;238madd:\u001b[0m')
print(' password/-p add a password entry') print(' password/-p add a password entry')
print(' note/-n add a note entry') print(' note/-n add a note entry')
print(' folder/-f add a new folder for entries') print(' folder/-f add a new folder for entries')
print('edit:') print('\u001b[38;5;15;48;5;238medit:\u001b[0m')
print(' rename/relocate/-r rename or relocate an entry') print(' rename/relocate/-r rename or relocate an entry')
print(' username/-u change the username of an entry') print(' username/-u change the username of an entry')
print(' password/-p change the password of an entry') print(' password/-p change the password of an entry')
print(' note/-n change the note attached to an entry') print(' note/-n change the note attached to an entry')
print(' url/-l change the url attached to an entry') print(' url/-l change the url attached to an entry')
print('copy:') print('\u001b[38;5;15;48;5;238mcopy:\u001b[0m')
print(' username/-u copy the username of an entry to your clipboard') 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(' password/-p copy the password of an entry to your clipboard')
print(' url/-l copy the URL 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(' note/-n copy the note of an entry to your clipboard')
print('gen:') print('\u001b[38;5;15;48;5;238mgen:\u001b[0m')
print(' update/-u generate a password for an existing entry\n') print(' update/-u generate a password for an existing entry\n')
print("Tip: You can quickly read an entry with 'sshyp /<entry name>'!") print("\u001b[38;5;0;48;5;15mTip:\u001b[0m You can quickly read an entry with 'sshyp /<entry name>'!")
print("When specifying entry names, do not include the file extension! '.gpg' is assumed!\n")
elif argument == 'version' or argument == '-v': elif argument == 'version' or argument == '-v':
print('\n////////////////////////////////////////////////////////') print('\n\u001b[38;5;0;48;5;15m////////////////////////////////////////////////////////\u001b[0m')
print('/ /') print('\u001b[38;5;0;48;5;15m/\u001b[38;5;15;48;5;238m '
print('/ sshyp Copyright (C) 2021-2022 Randall Winkhart /') '\u001b[38;5;0;48;5;15m/\u001b[0m')
print('/ /') print('\u001b[38;5;0;48;5;15m/\u001b[38;5;15;48;5;238m \u001b[38;5;0;48;5;15msshyp Copyright (C) 2021-2022 '
print('/ Version 2022.02.16.fr4 /') 'Randall Winkhart\u001b[38;5;15;48;5;238m \u001b[38;5;0;48;5;15m/\u001b[0m')
print('/ The High-Tech Shears Update /') print('\u001b[38;5;0;48;5;15m/\u001b[38;5;15;48;5;238m '
print('/ /') '\u001b[38;5;0;48;5;15m/\u001b[0m')
print('////////////////////////////////////////////////////////\n') print('\u001b[38;5;0;48;5;15m/\u001b[38;5;15;48;5;238m \u001b[38;5;0;48;5;15mVersion 1.0.0'
'\u001b[38;5;15;48;5;238m \u001b[38;5;0;48;5;15m/\u001b[0m')
print('\u001b[38;5;0;48;5;15m/\u001b[38;5;15;48;5;238m \u001b[38;5;0;48;5;15mThe Polished Trotters '
'Update\u001b[38;5;15;48;5;238m \u001b[38;5;0;48;5;15m/\u001b[0m')
print('\u001b[38;5;0;48;5;15m/\u001b[38;5;15;48;5;238m '
'\u001b[38;5;0;48;5;15m/\u001b[0m')
print('\u001b[38;5;0;48;5;15m////////////////////////////////////////////////////////\u001b[0m\n')
elif argument == 'license': elif argument == 'license':
print('\nThis program is free software: you can redistribute it and/or modify it under the terms of the GNU ' 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,' 'General\nPublic License as published by the Free Software Foundation, either version 3 of the License,'
@@ -170,25 +274,25 @@ def print_info(): # prints help text based on argument
' PARTICULAR PURPOSE.\nSee the GNU General Public License for more details.' ' PARTICULAR PURPOSE.\nSee the GNU General Public License for more details.'
'\n\nhttps://opensource.org/licenses/GPL-3.0\n') '\n\nhttps://opensource.org/licenses/GPL-3.0\n')
elif argument == 'add': elif argument == 'add':
print('\nUSAGE: sshyp add [FLAG]') print('\n\u001b[38;5;15;48;5;238mUSAGE: sshyp add {FLAG {<entry name>}}\u001b[0m\n')
print('Flags:') print('\u001b[38;5;0;48;5;15mFlags:\u001b[0m')
print('add:') print('\u001b[38;5;15;48;5;238madd:\u001b[0m')
print(' password/-p add a password entry') print(' password/-p add a password entry')
print(' note/-n add a note entry') print(' note/-n add a note entry')
print(' folder/-f add a new folder for entries\n') print(' folder/-f add a new folder for entries\n')
elif argument == 'edit': elif argument == 'edit':
print('\nUSAGE: sshyp edit [FLAG]') print('\n\u001b[38;5;15;48;5;238mUSAGE: sshyp edit {FLAG {<entry name>}}\u001b[0m\n')
print('Flags:') print('\u001b[38;5;0;48;5;15mFlags:\u001b[0m')
print('edit:') print('\u001b[38;5;15;48;5;238medit:\u001b[0m')
print(' rename/relocate/-r rename or relocate an entry') print(' rename/relocate/-r rename or relocate an entry')
print(' username/-u change the username of an entry') print(' username/-u change the username of an entry')
print(' password/-p change the password of an entry') print(' password/-p change the password of an entry')
print(' url/-l change the url attached to an entry') print(' url/-l change the url attached to an entry')
print(' note/-n change the note attached to an entry\n') print(' note/-n change the note attached to an entry\n')
elif argument == 'copy': elif argument == 'copy':
print('\nUSAGE: sshyp copy [FLAG]') print('\n\u001b[38;5;15;48;5;238mUSAGE: sshyp copy {FLAG {<entry name>}}\u001b[0m\n')
print('Flags:') print('\u001b[38;5;0;48;5;15mFlags:\u001b[0m')
print('copy:') print('\u001b[38;5;15;48;5;238mcopy:\u001b[0m')
print(' username/-u copy the username of an entry to your clipboard') 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(' password/-p copy the password of an entry to your clipboard')
print(' url/-l copy the URL of an entry to your clipboard') print(' url/-l copy the URL of an entry to your clipboard')
@@ -196,41 +300,76 @@ def print_info(): # prints help text based on argument
def no_arg(): # displays a list of entries and gives an option to select one for viewing 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'.\n") print("\nFor a list of usable commands, run 'sshyp help'.")
system(f"cd {directory}; ls -1 *") entry_list_gen()
_read_entry = str(input('\nEntry to read: ')) _read_entry = str(input('Entry to read: '))
system(f"gpg -d '{directory}{_read_entry.replace('/', '', 1)}.gpg'") if not Path(f"{directory}{_read_entry}.gpg").exists():
print() print(f"\n\u001b[38;5;1mError: entry ({_read_entry}) does not exist\u001b[0m\n")
s_exit(1)
_shm_folder, _shm_entry = shm_gen()
system(f"{gpg} -qd --output {tmp_dir}{_shm_folder}/{_shm_entry} '{directory}{_read_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 def read_shortcut(): # shortcut to quickly read an entry
system(f"gpg -d '{directory}{argument.replace('/', '', 1)}.gpg'") if not Path(f"{directory}{argument.replace('/', '', 1)}.gpg").exists():
print(f"\n\u001b[38;5;1mError: entry ({argument.replace('/', '', 1)}) does not exist\u001b[0m\n")
s_exit(1)
_shm_folder, _shm_entry = shm_gen()
system(f"{gpg} -qd --output {tmp_dir}{_shm_folder}/{_shm_entry} '{directory}{argument.replace('/', '', 1)}.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 def sync(): # calls sshync to sync changes to the user's server
print('\nSyncing entries with the server device...\n') print('\nSyncing entries with the server device...\n')
# check for deletions # check for deletions
system(f"ssh -i '{path.expanduser('~/.ssh/sshyp')}' -p {port} {username_ssh}@{ip} \"python -c 'import shear" system(f"ssh -i '{path.expanduser('~/.ssh/sshyp')}' -p {port} {username_ssh}@{ip} \"cd /bin; python -c "
f"; shear.check(\"'\"{client_device_name}\"'\")'\"") f"'import sshypRemote; sshypRemote.deletion_check(\"'\"{client_device_name}\"'\")'\"")
system(f"sftp -p -q -i '{path.expanduser('~/.ssh/sshyp')}' -P {port} {username_ssh}@{ip}:" system(f"scp -pqs -P {port} -i '{path.expanduser('~/.ssh/sshyp')}' {username_ssh}@{ip}:'/home/{username_ssh}"
f"'/home/{username_ssh}/.config/sshyp/deletion_database' {path.expanduser('~/.config/sshyp/')}") f"/.config/sshyp/deletion_database' {path.expanduser('~/.config/sshyp/')}")
try: try:
_deletion_database = open(path.expanduser('~/.config/sshyp/deletion_database')).readlines() _deletion_database = open(path.expanduser('~/.config/sshyp/deletion_database')).readlines()
except (FileNotFoundError, IndexError): except (FileNotFoundError, IndexError):
print('\nThe deletion database does not exist or is corrupted.\n') print('\n\u001b[38;5;1mError: the deletion database does not exist or is corrupted.\u001b[0m\n')
_deletion_database = None _deletion_database = None
s_exit() s_exit(1)
for _file in _deletion_database: for _file in _deletion_database:
if _file.endswith('/'): if _file[:-1].endswith('/'):
try: try:
if silent_sync != 1:
print(f"\u001b[38;5;208m{_file[:-1]}\u001b[0m has been sheared, removing...")
rmtree(f"{path.expanduser('~/.password-pasture/')}{_file[:-1]}") rmtree(f"{path.expanduser('~/.password-pasture/')}{_file[:-1]}")
except FileNotFoundError: except FileNotFoundError:
print('\nFolder does not exist locally.\n') if silent_sync != 1:
print('Folder does not exist locally.')
else: else:
try: try:
remove(f"{path.expanduser('~/.password-pasture/')}{_file[:-1]}") if silent_sync != 1:
except FileNotFoundError: print(f"\u001b[38;5;208m{_file[:-1]}\u001b[0m has been sheared, removing...")
print('\nFile does not exist locally.\n') remove(f"{path.expanduser('~/.password-pasture/')}{_file[:-1]}.gpg")
except (FileNotFoundError, IsADirectoryError):
if silent_sync != 1:
print('File 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;1mError: 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('/.password-pasture/', '')[:-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 # set permissions before uploading
system('find ' + directory + ' -type d -exec chmod -R 700 {} +') system('find ' + directory + ' -type d -exec chmod -R 700 {} +')
system('find ' + directory + ' -type f -exec chmod -R 600 {} +') system('find ' + directory + ' -type f -exec chmod -R 600 {} +')
@@ -239,15 +378,20 @@ def sync(): # calls sshync to sync changes to the user's server
def add_entry(): # adds a new entry def add_entry(): # adds a new entry
_shm_folder, _shm_entry = None, None # sets base-line values to avoid errors _shm_folder, _shm_entry = None, None # sets base-line values to avoid errors
_entry_name = str(input('\nName of entry: ')) if argument == 'add note' or argument == 'add -n' or argument == 'add password' or argument == 'add -p':
_entry_name = str(input('\nName of entry: '))
else:
_entry_name = argument.split(' ')
del _entry_name[:2]
_entry_name = ' '.join(_entry_name)
if _entry_name.startswith('/'): if _entry_name.startswith('/'):
_entry_name = _entry_name.replace('/', '', 1) _entry_name = _entry_name.replace('/', '', 1)
if argument == 'add note' or argument == 'add -n': if argument.startswith('add note') or argument.startswith('add -n'):
_shm_folder, _shm_entry = shm_gen() _shm_folder, _shm_entry = shm_gen()
system(f"nano {tmp_dir}{_shm_folder}/{_shm_entry}-n") system(f"nano {tmp_dir}{_shm_folder}/{_shm_entry}-n")
_notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'r').read() _notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'r').read()
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w').writelines('\n\n\n\n' + _notes + '\n\n') open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w').writelines('\n\n\n' + _notes)
elif argument == 'add password' or argument == 'add -p': elif argument.startswith('add password') or argument.startswith('add -p'):
_username = str(input('Username: ')) _username = str(input('Username: '))
_password = str(input('Password: ')) _password = str(input('Password: '))
_url = str(input('URL: ')) _url = str(input('URL: '))
@@ -258,90 +402,119 @@ def add_entry(): # adds a new entry
_notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'r').read() _notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'r').read()
else: else:
_notes = '' _notes = ''
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w').writelines(_username + '\n' + _password + '\n' + _url + '\n\n' open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w').writelines(_password + '\n' + _username + '\n' + _url +
+ _notes + '\n\n') '\n' + _notes)
print('\nEntry preview:')
entry_reader(f"{tmp_dir}{_shm_folder}/{_shm_entry}")
encrypt(_shm_folder, _shm_entry, _entry_name) encrypt(_shm_folder, _shm_entry, _entry_name)
system(f"gpg -d '{directory}{_entry_name}.gpg'")
def add_folder(): # creates a new folder def add_folder(): # creates a new folder
_folder_name = str(input('Name of new folder: ')) if argument == 'add folder' or argument == 'add -f':
_folder_name = str(input('Name of new folder: '))
else:
_folder_name = argument.split(' ')
del _folder_name[:2]
_folder_name = ' '.join(_folder_name)
if _folder_name.startswith('/'):
_folder_name = _folder_name.replace('/', '', 1)
Path(directory + _folder_name).mkdir(0o700) Path(directory + _folder_name).mkdir(0o700)
system(f"ssh -i '{path.expanduser('~/.ssh/sshyp')}' -p {port} {username_ssh}@{ip} \"mkdir -p '{directory_ssh}" system(f"ssh -i '{path.expanduser('~/.ssh/sshyp')}' -p {port} {username_ssh}@{ip} \"mkdir -p '{directory_ssh}"
f"{_folder_name}'\"") f"{_folder_name}'\"")
def rename(): # renames an entry or folder TODO delete original from server to prevent re-downloading def rename(): # renames an entry or folder
print() if argument == 'edit rename' or argument == 'edit relocate' or argument == 'edit -r':
system(f"cd {directory}; ls -1 *") # TODO replace with new list entry_list_gen()
_entry_name = str(input('\nWhat would you like to rename/relocate? ')) _entry_name = str(input('What would you like to rename/relocate? '))
else:
_entry_name = argument.split(' ')
del _entry_name[:2]
_entry_name = ' '.join(_entry_name)
if not Path(f"{directory}{_entry_name}.gpg").exists():
print(f"\n\u001b[38;5;1mError: entry ({_entry_name}) does not exist\u001b[0m\n")
s_exit(1)
_new_name = str(input('New Name: ')) _new_name = str(input('New Name: '))
if _entry_name.startswith('/'): if _entry_name.startswith('/'):
if _entry_name.replace('/', '', 1).__contains__('/'): _entry_name = _entry_name.replace('/', '', 1)
_folder, _sep, _folder_entry = _entry_name.replace('/', '', 1).partition('/') if _entry_name.endswith('/'):
move(f"{directory}{_folder}/{_folder_entry}.gpg", f"{directory}/{_new_name}.gpg") move(f"{directory}{_entry_name}", f"{directory}{_new_name}")
else: system(f"ssh -i '{path.expanduser('~/.ssh/sshyp')}' -p {port} {username_ssh}@{ip} \"mkdir -p '{directory_ssh}"
move(f"{directory}{_entry_name.replace('/', '', 1)}.gpg", f"{directory}{_new_name.replace('/', '', 1)}.gpg") f"{_new_name}'\"")
else: else:
move(f"{directory}{_entry_name}.gpg", f"{directory}{_new_name}.gpg") 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 def edit(): # edits the contents of an entry
_shm_folder, _shm_entry = None, None # sets base-line values to avoid errors _shm_folder, _shm_entry = None, None # sets base-line values to avoid errors
print() if argument == 'edit username' or argument == 'edit -u' or argument == 'edit password' or argument == 'edit -p' or \
system(f"cd {directory}; ls -1 *") # TODO replace with new list argument == 'edit url' or argument == 'edit -l' or argument == 'edit note' or argument == 'edit -n':
_entry_name = str(input('\nWhat file would you like to edit? ')) entry_list_gen()
if not Path(f"{directory}{_entry_name}.gpg").is_file(): _entry_name = str(input('What file would you like to edit? '))
print("\nYou are trying to edit a file that does not exist!\n") else:
s_exit() _entry_name = argument.split(' ')
del _entry_name[:2]
_entry_name = ' '.join(_entry_name)
if _entry_name.startswith('/'): if _entry_name.startswith('/'):
_entry_name = _entry_name.replace('/', '', 1) _entry_name = _entry_name.replace('/', '', 1)
if argument == 'edit username' or argument == 'edit -u': if not Path(f"{directory}{_entry_name}.gpg").is_file():
print(f"\n\u001b[38;5;1mError: entry ({_entry_name}) does not exist\u001b[0m\n")
s_exit(1)
_shm_folder, _shm_entry = shm_gen()
system(f"{gpg} -qd --output {tmp_dir}{_shm_folder}/{_shm_entry} '{directory}{_entry_name}.gpg'")
# compatibility check for GNU pass entries (ensuring they have at least four lines)
_lines = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'r').readlines()
if len(_lines) < 3:
while len(_lines) < 3:
_lines.append('\n')
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w').writelines(_lines)
if argument.startswith('edit username') or argument.startswith('edit -u'):
_detail = str(input('New Username: ')) _detail = str(input('New Username: '))
_shm_folder, _shm_entry = shm_gen()
system(f"gpg -d --output {tmp_dir}{_shm_folder}/{_shm_entry} '{directory}{_entry_name}.gpg'")
replace_line(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 0, _detail + '\n')
elif argument == 'edit password' or argument == 'edit -p':
_detail = str(input('New Password: '))
_shm_folder, _shm_entry = shm_gen()
system(f"gpg -d --output {tmp_dir}{_shm_folder}/{_shm_entry} '{directory}{_entry_name}.gpg'")
replace_line(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 1, _detail + '\n') replace_line(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 1, _detail + '\n')
elif argument == 'edit url' or argument == 'edit -l': elif argument.startswith('edit password') or argument.startswith('edit -p'):
_detail = str(input('New Password: '))
replace_line(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 0, _detail + '\n')
elif argument.startswith('edit url') or argument.startswith('edit -l'):
_detail = str(input('New URL: ')) _detail = str(input('New URL: '))
_shm_folder, _shm_entry = shm_gen()
system(f"gpg -d --output {tmp_dir}{_shm_folder}/{_shm_entry} '{directory}{_entry_name}.gpg'")
replace_line(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 2, _detail + '\n') replace_line(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 2, _detail + '\n')
elif argument == 'edit note' or argument == 'edit -n': elif argument.startswith('edit note') or argument.startswith('edit -n'):
_shm_folder, _shm_entry = shm_gen()
system(f"gpg -d --output {tmp_dir}{_shm_folder}/{_shm_entry} '{directory}{_entry_name}.gpg'")
edit_note(_shm_folder, _shm_entry) edit_note(_shm_folder, _shm_entry)
remove(f"{directory}{_entry_name}.gpg") remove(f"{directory}{_entry_name}.gpg")
print('\nEntry preview:')
entry_reader(f"{tmp_dir}{_shm_folder}/{_shm_entry}")
encrypt(_shm_folder, _shm_entry, _entry_name) encrypt(_shm_folder, _shm_entry, _entry_name)
system(f"gpg -d '{directory}{_entry_name}.gpg'")
def gen(): # generates a password for a new or an existing entry 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 _username, _url, _notes = None, None, None # sets base-line values to avoid errors
if argument == 'gen update' or argument == 'gen -u': if argument == 'gen update' or argument == 'gen -u' or argument == 'gen':
print() if argument == 'gen update' or argument == 'gen -u':
system(f"cd {directory}; ls -1 *") # TODO replace with new list entry_list_gen()
_entry_name = str(input('\nName of service: ')) else:
print()
_entry_name = str(input('Name of service: '))
elif argument.startswith('gen update') or argument.startswith('gen -u'):
_entry_name = argument.split(' ')
del _entry_name[:2]
_entry_name = ' '.join(_entry_name)
else:
_entry_name = argument.split(' ')
del _entry_name[:1]
_entry_name = ' '.join(_entry_name)
if _entry_name.startswith('/'): if _entry_name.startswith('/'):
_entry_name = _entry_name.replace('/', '', 1) _entry_name = _entry_name.replace('/', '', 1)
if argument == 'gen update' or argument == 'gen -u': if argument.startswith('gen update') or argument.startswith('gen -u'):
if not Path(f"{directory}{_entry_name}.gpg").is_file(): if not Path(f"{directory}{_entry_name}.gpg").is_file():
print("\nYou are trying to edit a file that does not exist!\n") print(f"\n\u001b[38;5;1mError: entry ({_entry_name}) does not exist\u001b[0m\n")
s_exit() s_exit(1)
if argument != 'gen update' and argument != 'gen -u':
_username = str(input('Username: '))
_pass_length = int(input('How many characters should be in the password? '))
_pass_type = str(input('Should the password be simple (for compatibility) or complex (for security)? (s/C) '))
if _pass_type.lower() == 's':
_pass_type = string.ascii_letters + string.digits
else: else:
_pass_type = string.ascii_letters + string.digits + string.punctuation _username = str(input('Username: '))
_pass_gen = ''.join(random.SystemRandom().choice(_pass_type) for _ in range(_pass_length)) _password = pass_gen()
if argument != 'gen update' and argument != 'gen -u': if not argument.startswith('gen update') and not argument.startswith('gen -u'):
_url = str(input('URL: ')) _url = str(input('URL: '))
_add_note = input('Add a note to this entry? (y/N) ') _add_note = input('Add a note to this entry? (y/N) ')
_shm_folder, _shm_entry = shm_gen() _shm_folder, _shm_entry = shm_gen()
@@ -350,185 +523,191 @@ def gen(): # generates a password for a new or an existing entry
_notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'r').read() _notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'r').read()
else: else:
_notes = '' _notes = ''
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w').writelines(_username + '\n' + _pass_gen + '\n' + _url + '\n\n' open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w').writelines(_password + '\n' + _username + '\n' + _url + '\n'
+ _notes + '\n\n') + _notes)
print('\nEntry preview:')
entry_reader(f"{tmp_dir}{_shm_folder}/{_shm_entry}")
encrypt(_shm_folder, _shm_entry, _entry_name) encrypt(_shm_folder, _shm_entry, _entry_name)
system(f"gpg -d '{directory}{_entry_name}.gpg'")
else: else:
_shm_folder, _shm_entry = shm_gen() _shm_folder, _shm_entry = shm_gen()
system(f"gpg -d --output {tmp_dir}{_shm_folder}/{_shm_entry} '{directory}{_entry_name}.gpg'") system(f"{gpg} -qd --output {tmp_dir}{_shm_folder}/{_shm_entry} '{directory}{_entry_name}.gpg'")
replace_line(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 1, _pass_gen + '\n') replace_line(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 0, _password + '\n')
remove(f"{directory}{_entry_name}.gpg") remove(f"{directory}{_entry_name}.gpg")
print('\nEntry preview:')
entry_reader(f"{tmp_dir}{_shm_folder}/{_shm_entry}")
encrypt(_shm_folder, _shm_entry, _entry_name) encrypt(_shm_folder, _shm_entry, _entry_name)
system(f"gpg -d '{directory}{_entry_name}.gpg'")
def copy_data(): # copies a specified field of an entry to the clipboard def copy_data(): # copies a specified field of an entry to the clipboard
print() if argument == 'copy username' or argument == 'copy -u' or argument == 'copy password' or argument == 'copy -p' \
system(f"cd {directory}; ls -1 *") # TODO replace with new list or argument == 'copy url' or argument == 'copy -l' or argument == 'copy note' or argument == 'copy -n':
_read_entry = str(input('\nEntry to copy: ')) entry_list_gen()
if not Path(f"{directory}{_read_entry}.gpg").is_file(): _read_entry = str(input('Entry to copy: '))
print("\nThe file does not exist!\n")
s_exit()
_shm_folder, _shm_entry = shm_gen()
system(f"gpg -d --output {tmp_dir}{_shm_folder}/{_shm_entry} '{directory}{_read_entry}.gpg'")
_copy_line = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'r').readlines()
if Path("/data/data/com.termux").exists(): # checks for Termux, Wayland, or X
if argument == 'copy username' or argument == 'copy -u':
system('termux-clipboard-set ' + "'" + _copy_line[0].replace('\n', '').replace("'", "'\\''") + "'")
elif argument == 'copy password' or argument == 'copy -p':
system('termux-clipboard-set ' + "'" + _copy_line[1].replace('\n', '').replace("'", "'\\''") + "'")
elif argument == 'copy url' or argument == 'copy -l':
system('termux-clipboard-set ' + "'" + _copy_line[2].replace('\n', '').replace("'", "'\\''") + "'")
elif argument == 'copy note' or argument == 'copy -n':
system('termux-clipboard-set ' + "'" + _copy_line[4].replace('\n', '').replace("'", "'\\''") + "'")
elif environ.get('WAYLAND_DISPLAY') == 'wayland-0':
if argument == 'copy username' or argument == 'copy -u':
system('wl-copy ' + "'" + _copy_line[0].replace('\n', '').replace("'", "'\\''") + "'")
elif argument == 'copy password' or argument == 'copy -p':
system('wl-copy ' + "'" + _copy_line[1].replace('\n', '').replace("'", "'\\''") + "'")
elif argument == 'copy url' or argument == 'copy -l':
system('wl-copy ' + "'" + _copy_line[2].replace('\n', '').replace("'", "'\\''") + "'")
elif argument == 'copy note' or argument == 'copy -n':
system('wl-copy ' + "'" + _copy_line[4].replace('\n', '').replace("'", "'\\''") + "'")
else: else:
if argument == 'copy username' or argument == 'copy -u': _read_entry = argument.split(' ')
system('echo -n ' + "'" + _copy_line[0].replace('\n', '').replace("'", "'\\''") + "'" + ' | xclip -sel c') del _read_entry[:2]
elif argument == 'copy password' or argument == 'copy -p': _read_entry = ' '.join(_read_entry)
if not Path(f"{directory}{_read_entry}.gpg").is_file():
print(f"\n\u001b[38;5;1mError: entry ({_read_entry}) does not exist\u001b[0m\n")
s_exit(1)
_shm_folder, _shm_entry = shm_gen()
system(f"{gpg} -qd --output {tmp_dir}{_shm_folder}/{_shm_entry} '{directory}{_read_entry}.gpg'")
_copy_line = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'r').readlines()
if uname()[0] == 'Haiku': # Haiku clipboard detection
if argument.startswith('copy username') or argument.startswith('copy -u'):
system('clipboard -c ' + "'" + _copy_line[1].replace('\n', '').replace("'", "'\\''") + "'")
elif argument.startswith('copy password') or argument.startswith('copy -p'):
system('clipboard -c ' + "'" + _copy_line[0].replace('\n', '').replace("'", "'\\''") + "'")
elif argument.startswith('copy url') or argument.startswith('copy -l'):
system('clipboard -c ' + "'" + _copy_line[2].replace('\n', '').replace("'", "'\\''") + "'")
elif argument.startswith('copy note') or argument.startswith('copy -n'):
system('clipboard -c ' + "'" + _copy_line[3].replace('\n', '').replace("'", "'\\''") + "'")
Popen('sleep 30; clipboard -r', shell=True)
elif Path("/data/data/com.termux").exists(): # Termux (Android) clipboard detection
if argument.startswith('copy username') or argument.startswith('copy -u'):
system('termux-clipboard-set ' + "'" + _copy_line[1].replace('\n', '').replace("'", "'\\''") + "'")
elif argument.startswith('copy password') or argument.startswith('copy -p'):
system('termux-clipboard-set ' + "'" + _copy_line[0].replace('\n', '').replace("'", "'\\''") + "'")
elif argument.startswith('copy url') or argument.startswith('copy -l'):
system('termux-clipboard-set ' + "'" + _copy_line[2].replace('\n', '').replace("'", "'\\''") + "'")
elif argument.startswith('copy note') or argument.startswith('copy -n'):
system('termux-clipboard-set ' + "'" + _copy_line[3].replace('\n', '').replace("'", "'\\''") + "'")
Popen("sleep 30; termux-clipboard-set ''", shell=True)
elif environ.get('WAYLAND_DISPLAY') == 'wayland-0': # Wayland clipboard detection
if argument.startswith('copy username') or argument.startswith('copy -u'):
system('wl-copy ' + "'" + _copy_line[1].replace('\n', '').replace("'", "'\\''") + "'")
elif argument.startswith('copy password') or argument.startswith('copy -p'):
system('wl-copy ' + "'" + _copy_line[0].replace('\n', '').replace("'", "'\\''") + "'")
elif argument.startswith('copy url') or argument.startswith('copy -l'):
system('wl-copy ' + "'" + _copy_line[2].replace('\n', '').replace("'", "'\\''") + "'")
elif argument.startswith('copy note') or argument.startswith('copy -n'):
system('wl-copy ' + "'" + _copy_line[3].replace('\n', '').replace("'", "'\\''") + "'")
Popen('sleep 30; wl-copy -c', shell=True)
else: # X11 clipboard detection
if argument.startswith('copy username') or argument.startswith('copy -u'):
system('echo -n ' + "'" + _copy_line[1].replace('\n', '').replace("'", "'\\''") + "'" + ' | xclip -sel c') system('echo -n ' + "'" + _copy_line[1].replace('\n', '').replace("'", "'\\''") + "'" + ' | xclip -sel c')
elif argument == 'copy url' or argument == 'copy -l': elif argument.startswith('copy password') or argument.startswith('copy -p'):
system('echo -n ' + "'" + _copy_line[0].replace('\n', '').replace("'", "'\\''") + "'" + ' | xclip -sel c')
elif argument.startswith('copy url') or argument.startswith('copy -l'):
system('echo -n ' + "'" + _copy_line[2].replace('\n', '').replace("'", "'\\''") + "'" + ' | xclip -sel c') system('echo -n ' + "'" + _copy_line[2].replace('\n', '').replace("'", "'\\''") + "'" + ' | xclip -sel c')
elif argument == 'copy note' or argument == 'copy -n': elif argument.startswith('copy note') or argument.startswith('copy -n'):
system('echo -n ' + "'" + _copy_line[4].replace('\n', '').replace("'", "'\\''") + "'" + ' | xclip -sel c') system('echo -n ' + "'" + _copy_line[3].replace('\n', '').replace("'", "'\\''") + "'" + ' | xclip -sel c')
Popen("sleep 30; echo -n '' | xclip -sel c", shell=True)
rmtree(f"{tmp_dir}{_shm_folder}") rmtree(f"{tmp_dir}{_shm_folder}")
def remove_data(): # deletes an entry from the server and flags it for local deletion on sync def remove_data(): # deletes an entry from the server and flags it for local deletion on sync
print() if argument == 'shear' or argument == '-rm':
system(f"cd {directory}; ls -1 *") # TODO replace with new list entry_list_gen()
_entry_name = str(input('\nWhat would you like to delete? ')) _entry_name = str(input('What would you like to delete? '))
else:
_entry_name = argument.split(' ')
del _entry_name[:1]
_entry_name = ' '.join(_entry_name)
if _entry_name.startswith('/'): if _entry_name.startswith('/'):
_entry_name = _entry_name.replace('/', '', 1) _entry_name = _entry_name.replace('/', '', 1)
try: try:
system(f"gpg -d --output {tmp_dir}sshyp.lock {path.expanduser('~/.config/sshyp/lock.gpg')}") system(f"{gpg} -qd --output {tmp_dir}sshyp.lock {path.expanduser('~/.config/sshyp/lock.gpg')}")
_unlock = open(f"{tmp_dir}sshyp.lock", 'r').readlines() _unlock = open(f"{tmp_dir}sshyp.lock", 'r').readlines()
remove(f"{tmp_dir}sshyp.lock") remove(f"{tmp_dir}sshyp.lock")
except FileNotFoundError: except FileNotFoundError:
print('\nAccess denied.\n') print('\nAccess denied.\n')
s_exit() s_exit(1)
system(f"ssh -i '{path.expanduser('~/.ssh/sshyp')}' -p {port} {username_ssh}@{ip} \"python -c 'import shear" system(f"ssh -i '{path.expanduser('~/.ssh/sshyp')}' -p {port} {username_ssh}@{ip} \"cd /bin; python -c "
f"; shear.delete(\"'\"{_entry_name}\"'\")'\"") f"'import sshypRemote; sshypRemote.delete(\"'\"{_entry_name}\"'\")'\"")
# program start sequence if __name__ == "__main__":
try:
silent_sync = 0
# retrieve typed argument
argument_list, argument = argv, ''
del argument_list[0]
for _argument in argument_list:
argument += _argument + ' '
argument = argument[:-1]
if uname()[0] == 'Haiku': # set proper gpg command for OS
gpg = 'gpg --pinentry-mode loopback'
else:
gpg = 'gpg'
# retrieve typed argument # import saved userdata
argument_list = argv if argument != 'tweak':
i, argument = 0, '' device_type = ''
for _ in argument_list: tmp_dir = path.expanduser('~/.config/sshyp/tmp/')
while i < len(argument_list) - 1: try:
i += 1 device_type = open(path.expanduser('~/.config/sshyp/sshyp-device')).read().strip()
argument += argument_list[i] + ' ' if device_type == 'c':
argument = argument[:-1] gpg_id = open(path.expanduser('~/.config/sshyp/sshyp-gpg')).read().strip()
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 = '/home/' + username_ssh + '/.password-pasture/'
client_device_name = listdir(path.expanduser('~/.config/sshyp/devices'))[0]
except FileNotFoundError:
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)
# import saved userdata # run function based on argument
if argument != 'tweak': error = 0 # create an error flag to determine if syncing should occur at end
device_type = '' if argument.startswith('/'):
tmp_dir = path.expanduser('~/.config/sshyp/tmp/') read_shortcut()
try: elif argument == '':
device_type = open(path.expanduser('~/.config/sshyp/sshyp-device')).read().strip() no_arg()
if device_type == 'c': elif argument == 'help' or argument == '--help' or argument == '-h' or argument == 'license' or argument \
gpg_id = open(path.expanduser('~/.config/sshyp/sshyp-gpg')).read().strip() == 'version' or argument == '-v' or argument == 'add' or argument == 'edit' or argument == 'copy':
ssh_info = sshync.get_profile(path.expanduser('~/.config/sshyp/sshyp.sshync')) print_info()
username_ssh = ssh_info[0].replace('\n', '') elif argument.startswith('add note') or argument.startswith('add -n') or argument.startswith('add password') \
ip = ssh_info[1].replace('\n', '') or argument.startswith('add -p'):
port = ssh_info[2].replace('\n', '') add_entry()
directory = str(ssh_info[3].replace('\n', '')) elif argument.startswith('add folder') or argument.startswith('add -f'):
directory_ssh = '/home/' + username_ssh + '/.password-pasture/' add_folder()
client_device_name = listdir(path.expanduser('~/.config/sshyp/devices'))[0] elif argument.startswith('edit rename') or argument.startswith('edit relocate') or argument.startswith(
except FileNotFoundError: 'edit -r'):
if device_type.lower() != 's': silent_sync = 1
print('\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!') rename()
print("Not all necessary configuration files are present. Please run 'sshyp tweak'!") elif argument.startswith('edit username') or argument.startswith('edit -u') or argument.startswith(
print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n') 'edit password') \
s_exit() or argument.startswith('edit -p') or argument.startswith('edit url') or argument.startswith(
else: 'edit -l') or \
try: argument.startswith('edit note') or argument.startswith('edit -n'):
tweak() edit()
s_exit() elif argument.startswith('gen'):
except KeyboardInterrupt: gen()
print('\n') elif argument.startswith('copy username') or argument.startswith('copy -u') or argument.startswith(
s_exit() 'copy password') \
or argument.startswith('copy -p') or argument.startswith('copy url') or argument.startswith('copy -l') \
or argument.startswith('copy note') or argument.startswith('copy -n'):
copy_data()
elif argument.startswith('shear') or argument.startswith('-rm'):
remove_data()
elif argument == 'sync' or argument == '-s':
sync()
else:
error = 1 # set error flag to 1 to stop syncing
print("\nArgument error! Please run 'sshyp help' for a list of usable commands.\n")
s_exit(1)
# run function based on argument # sync at end of program if any changes were made
error = 0 # create an error flag to determine if syncing should occur at end if argument != '' and argument != 'help' and argument != '--help' and argument != '-h' and argument != \
if argument.startswith('/'): 'license' and argument != 'add' and argument != 'sync' and argument != 'edit' and not argument\
read_shortcut() .startswith('copy username') and not argument.startswith('copy -u') and not argument\
elif argument == '': .startswith('copy password') and not argument.startswith('copy -p') and not argument\
try: .startswith('copy url') and not argument.startswith('copy -l') and not argument.startswith('copy note')\
no_arg() and not argument.startswith('copy -n') and argument != 'copy' and argument != 'version' and argument \
except KeyboardInterrupt: != '-v' and error == 0 and argument.startswith('/') is False:
print('\n') sync()
s_exit()
elif argument == 'help' or argument == '--help' or argument == '-h' or argument == 'license' or argument == 'version' \
or argument == '-v' or argument == 'add' or argument == 'edit' or argument == 'copy':
print_info()
elif argument == 'add note' or argument == 'add -n' or argument == 'add password' or argument == 'add -p':
try:
add_entry()
except KeyboardInterrupt:
print('\n')
s_exit()
elif argument == 'add folder' or argument == 'add -f':
try:
add_folder()
except KeyboardInterrupt:
print('\n')
s_exit()
elif argument == 'edit rename' or argument == 'edit relocate' or argument == 'edit -r':
try:
rename()
except KeyboardInterrupt:
print('\n')
s_exit()
elif argument == 'edit username' or argument == 'edit -u' or argument == 'edit password' or argument == 'edit -p' or \
argument == 'edit url' or argument == 'edit -l' or argument == 'edit note' or argument == 'edit -n':
try:
edit()
except KeyboardInterrupt:
print('\n')
s_exit()
elif argument == 'gen' or argument == 'gen update' or argument == 'gen -u':
try:
gen()
except KeyboardInterrupt:
print('\n')
s_exit()
elif argument == 'copy username' or argument == 'copy -u' or argument == 'copy password' or argument == 'copy -p' or \
argument == 'copy url' or argument == 'copy -l' or argument == 'copy note' or argument == 'copy -n':
try:
copy_data()
except KeyboardInterrupt:
print('\n')
s_exit()
elif argument == 'shear' or argument == '-rm':
try:
remove_data()
except KeyboardInterrupt:
print('\n')
s_exit()
elif argument == 'sync' or argument == '-s':
sync()
else:
error = 1 # set error flag to 1 to stop syncing
print("\nArgument error! Please run 'sshyp help' for a list of usable commands.\n")
s_exit()
# sync at end of program if any changes were made s_exit(0)
if argument != '' and argument != 'help' and argument != '--help' and argument != '-h' and argument != 'license' and \
argument != 'add' and argument != 'sync' and argument != 'edit' and argument != 'copy username' and argument \ except KeyboardInterrupt:
!= 'copy -u' and argument != 'copy password' and argument != 'copy -p' and argument != 'copy url' and argument \ print('\n')
!= 'copy -l' and argument != 'copy note' and argument != 'copy -n' and argument != 'copy' and argument \ s_exit(0)
!= 'version' and argument != '-v' and error == 0 and \
argument.startswith('/') is False:
sync()
+17 -9
View File
@@ -1,8 +1,8 @@
#!/usr/bin/python3 #!/bin/python3
# external modules # external modules
from os import remove, listdir from os import listdir, remove, walk
from os.path import expanduser from os.path import expanduser
from shutil import rmtree from shutil import rmtree
@@ -14,24 +14,32 @@ def delete(_file_path):
try: try:
rmtree(f"{expanduser('~/.password-pasture/')}{_file_path}") rmtree(f"{expanduser('~/.password-pasture/')}{_file_path}")
except FileNotFoundError: except FileNotFoundError:
print('\nFolder does not exist remotely.') print('Folder does not exist remotely.')
else: else:
try: try:
remove(f"{expanduser('~/.password-pasture/')}{_file_path}.gpg") remove(f"{expanduser('~/.password-pasture/')}{_file_path}.gpg")
except FileNotFoundError: except FileNotFoundError:
print('\nFile does not exist remotely.\n') print('File does not exist remotely.')
for _device_name in listdir(expanduser('~/.config/sshyp/devices')): for _device_name in listdir(expanduser('~/.config/sshyp/devices')):
open(f"{expanduser('~/.config/sshyp/deleted/')}{_file_path.replace('/', '@')}.{_device_name}.del", 'w') open(f"{expanduser('~/.config/sshyp/deleted/')}{_file_path.replace('/', '@')}^&*{_device_name}", 'w')
def check(_client_device_name): def deletion_check(_client_device_name):
open(expanduser('~/.config/sshyp/deletion_database'), 'w').write('') open(expanduser('~/.config/sshyp/deletion_database'), 'w').write('')
for _file in listdir(expanduser('~/.config/sshyp/deleted')): for _file in listdir(expanduser('~/.config/sshyp/deleted')):
_file_path = _file.replace('.gpg', '').replace('@', '/').split('.')[0] _file_path = _file.replace('@', '/').split('^&*')[0]
_device = _file.replace('.gpg', '').split('.')[1] _device = _file.split('^&*')[1]
if _device == _client_device_name: if _device == _client_device_name:
try: try:
remove(f"{expanduser('~/.config/sshyp/deleted/')}{_file}") remove(f"{expanduser('~/.config/sshyp/deleted/')}{_file}")
except FileNotFoundError: except FileNotFoundError:
pass pass
open(expanduser('~/.config/sshyp/deletion_database'), 'a').write(_file_path + '.gpg\n') open(expanduser('~/.config/sshyp/deletion_database'), 'a').write(_file_path + '\n')
def folder_check():
open(expanduser('~/.config/sshyp/folder_database'), 'w').write('')
for _root, _directories, _files in walk(expanduser('~/.password-pasture')):
for _dir in _directories:
open(expanduser('~/.config/sshyp/folder_database'), 'a')\
.write(f"{_root.replace(expanduser('~'), '')}/{_dir}\n")
+308
View File
@@ -1,3 +1,311 @@
sshyp 2022.02.16.fr4.7
The High-Tech Shears Update - Patch 7
This release enables recursive folder syncing.
Note: Upgrading from releases older than fr4.7 requires the configuration ("sshyp tweak") to be re-done, as the configuration process has changed.
Changes:
- re-wrote folder checking function to fetch subdirectories recursively
- added in user-facing messages for when folders are being synced or entries are being removed
- fixed a typo preventing folder syncing from working (actually tested this time)
&&
Planned for next major update (fr5, The sshyp Doing a Split Update):
sshyp:
** sshyp has recieved a major visual overhaul
^ this includes the new file list and thematic text art
sshync:
** sshync has been split into a separate package and now has standalone functionality as a backup/syncing utility
^ more details will be available in the sshync repo, once it is created
&&
Planned for next, next major update (fr6, The Farmer Shearing the sshyp Update):
** imporove password generator to guarantee more secure results
** enforce permissions on the server-side
** allow for copying entire notes (not just first line)
** ability to pass entries as arguments for more functions
&&
Backlog:
** create separate gui frontend targetting mobile and desktop Linux
** auto-completion on tab (currently unsure of best way to make this work)
<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
sshyp 2022.02.16.fr4.6
The High-Tech Shears Update - Patch 6
This release addresses a critical typo.
Note: Upgrading from releases older than fr4.6 requires the configuration ("sshyp tweak") to be re-done, as the configuration process has changed.
Changes:
- corrected a typo error causing folder syncing issues
&&
Planned for next major update (fr5, The sshyp Doing a Split Update):
sshyp:
** sshyp has recieved a major visual overhaul
^ this includes the new file list and thematic text art
sshync:
** sshync has been split into a separate package and now has standalone functionality as a backup/syncing utility
^ more details will be available in the sshync repo, once it is created
&&
Planned for next, next major update (fr6, The Farmer Shearing the sshyp Update):
** when syncing, a check is made to see if any folders are not on all devices - if the check comes back true, it is corrected (in sshyp)
** imporove password generator to guarantee more secure results
** enforce permissions on the server-side
** allow for copying entire notes (not just first line)
** ability to pass entries as arguments for more functions
&&
Backlog:
** create separate gui frontend targetting mobile and desktop Linux
** auto-completion on tab (currently unsure of best way to make this work)
<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
sshyp 2022.02.16.fr4.5
The High-Tech Shears Update - Patch 5
This release addresses a critical syntax error.
Note: Upgrading from releases older than fr4.5 requires the configuration ("sshyp tweak") to be re-done, as the configuration process has changed.
Changes:
- corrected a syntax error causing critical functionality issues
&&
Planned for next major update (fr5, The sshyp Doing a Split Update):
sshyp:
** sshyp has recieved a major visual overhaul
^ this includes the new file list and thematic text art
sshync:
** sshync has been split into a separate package and now has standalone functionality as a backup/syncing utility
^ more details will be available in the sshync repo, once it is created
&&
Planned for next, next major update (fr6, The Farmer Shearing the sshyp Update):
** when syncing, a check is made to see if any folders are not on all devices - if the check comes back true, it is corrected (in sshyp)
** imporove password generator to guarantee more secure results
** enforce permissions on the server-side
** allow for copying entire notes (not just first line)
** ability to pass entries as arguments for more functions
&&
Backlog:
** create separate gui frontend targetting mobile and desktop Linux
** auto-completion on tab (currently unsure of best way to make this work)
<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
sshyp 2022.02.16.fr4.4
The High-Tech Shears Update - Patch 4
This release automatically generates user-created folders on new client devices.
Note: Upgrading to releases in the fr4 series requires the configuration ("sshyp tweak") to be re-done, as a device name now must be set (for multi-client deletion). Even upgrading from fr4.3 to fr4.4 will require re-running "sshyp tweak" due to file name changes.
Features:
- implemented proper folder syncing for new clients and clients missing folders that exist server-side
Changes:
- old device names are now automatically deleted (locally) when running "sshyp tweak", to prevent conflicts
&&
Planned for next major update (fr5, The sshyp Doing a Split Update):
sshyp:
** sshyp has recieved a major visual overhaul
^ this includes the new file list and thematic text art
sshync:
** sshync has been split into a separate package and now has standalone functionality as a backup/syncing utility
^ more details will be available in the sshync repo, once it is created
&&
Planned for next, next major update (fr6, The Farmer Shearing the sshyp Update):
** when syncing, a check is made to see if any folders are not on all devices - if the check comes back true, it is corrected (in sshyp)
** imporove password generator to guarantee more secure results
** enforce permissions on the server-side
** allow for copying entire notes (not just first line)
** ability to pass entries as arguments for more functions
&&
Backlog:
** create separate gui frontend targetting mobile and desktop Linux
** auto-completion on tab (currently unsure of best way to make this work)
<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
sshyp 2022.02.16.fr4.3
The High-Tech Shears Update - Patch 3
This release addresses a bug with local entry deletion.
Note: Upgrading to releases in the fr4 series requires the configuration ("sshyp tweak") to be re-done, as a device name now must be set (for multi-client deletion).
Changes:
- fixed an issue where entries could not be deleted locally
&&
Planned for next major update (fr5, The sshyp Doing a Split Update):
sshyp:
** sshyp has recieved a major visual overhaul
^ this includes the new file list and thematic text art
sshync:
** sshync has been split into a separate package and now has standalone functionality as a backup/syncing utility
^ more details will be available in the sshync repo, once it is created
&&
Planned for next, next major update (fr6, The Farmer Shearing the sshyp Update):
** when syncing, a check is made to see if any folders are not on all devices - if the check comes back true, it is corrected (in sshyp)
** imporove password generator to guarantee more secure results
** enforce permissions on the server-side
** allow for copying entire notes (not just first line)
** ability to pass entries as arguments for more functions
&&
Backlog:
** create separate gui frontend targetting mobile and desktop Linux
** auto-completion on tab (currently unsure of best way to make this work)
<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
sshyp 2022.02.16.fr4.2
The High-Tech Shears Update - Patch 2
This release re-addresses a bug with local folder deletion.
Note: Upgrading to releases in the fr4 series requires the configuration ("sshyp tweak") to be re-done, as a device name now must be set (for multi-client deletion).
Changes:
- fixed an issue where folders could not be deleted locally (actually, this time)
- properly handled an exception for when the user forgets a following '/' when attempting to delete a folder
&&
Planned for next major update (fr5, The sshyp Doing a Split Update):
sshyp:
** sshyp has recieved a major visual overhaul
^ this includes the new file list and thematic text art
sshync:
** sshync has been split into a separate package and now has standalone functionality as a backup/syncing utility
^ more details will be available in the sshync repo, once it is created
&&
Planned for next, next major update (fr6, The Farmer Shearing the sshyp Update):
** when syncing, a check is made to see if any folders are not on all devices - if the check comes back true, it is corrected (in sshyp)
** imporove password generator to guarantee more secure results
** enforce permissions on the server-side
** allow for copying entire notes (not just first line)
** ability to pass entries as arguments for more functions
&&
Backlog:
** create separate gui frontend targetting mobile and desktop Linux
** auto-completion on tab (currently unsure of best way to make this work)
<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
sshyp 2022.02.16.fr4.1
The High-Tech Shears Update - Patch 1
This release addresses a bug with local folder deletion.
Note: Upgrading to releases in the fr4 series requires the configuration ("sshyp tweak") to be re-done, as a device name now must be set (for multi-client deletion).
Changes:
- fixed an issue where folders could not be deleted locally
- optimized deletion flags
&&
Planned for next major update (fr5, The sshyp Doing a Split Update):
sshyp:
** sshyp has recieved a major visual overhaul
^ this includes the new file list and thematic text art
sshync:
** sshync has been split into a separate package and now has standalone functionality as a backup/syncing utility
^ more details will be available in the sshync repo, once it is created
&&
Planned for next, next major update (fr6, The Farmer Shearing the sshyp Update):
** when syncing, a check is made to see if any folders are not on all devices - if the check comes back true, it is corrected (in sshyp)
** imporove password generator to guarantee more secure results
** enforce permissions on the server-side
** allow for copying entire notes (not just first line)
** ability to pass entries as arguments for more functions
&&
Backlog:
** create separate gui frontend targetting mobile and desktop Linux
** auto-completion on tab (currently unsure of best way to make this work)
<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
sshyp 2022.02.16.fr4 sshyp 2022.02.16.fr4
The High-Tech Shears Update The High-Tech Shears Update
+16 -26
View File
@@ -1,6 +1,6 @@
.TH sshyp 1 "16 February 2022" "2022.02.16.fr4" "sshyp man page" .TH sshyp 1 "22 May 2022" "v1.0.0" "sshyp man page"
.SH NAME .SH NAME
sshyp \- A very simple self-hosted, synchronized password manager. Alternative to GNU pass. sshyp \- A very simple self-hosted, synchronized password manager for UNIX systems. Alternative to (and compatible with) pass/password-store.
.SH SYNOPSIS .SH SYNOPSIS
sshyp [OPTION] [[FLAG]] [/<entry name>] sshyp [OPTION] [[FLAG]] [/<entry name>]
.SH DESCRIPTION .SH DESCRIPTION
@@ -8,40 +8,33 @@ sshyp is a lightweight password and note management program written in Python. s
.SH EXAMPLES .SH EXAMPLES
Setting up sshyp for the first time or altering its configuration (also recommended after major updates): Setting up sshyp for the first time or altering its configuration (also recommended after major updates):
sshyp tweak sshyp tweak
*follow the prompts
Viewing the entry database:
sshyp
Reading an existing entry saved as ~/.password-pasture/development/github.gpg: Reading an existing entry saved as ~/.password-pasture/development/github.gpg:
sshyp /development/github sshyp /development/github
Copying the password of an existing entry saved as ~/.password-pasture/financial/bank.gpg: Copying the password of an existing entry saved as ~/.password-pasture/financial/bank.gpg:
sshyp copy -p sshyp copy -p /financial/bank
*answer the prompt with "/financial/bank"
*enter decryption password
Editing the username of an existing entry saved as ~/.password-pasture/game.gpg: Editing the username of an existing entry saved as ~/.password-pasture/game.gpg:
sshyp edit -u sshyp edit -u game
*answer the first prompt with "game"
*follow the prompts
Making a new entry using the built-in password generator: Making a new entry saved as ~/.password-pasture/school/university.gpg using the built-in password generator:
sshyp gen sshyp gen /school/university
*follow the prompts
Creating a note-only entry: Creating a note-only entry saved as ~/.password-pasture/notes/testNote.gpg:
sshyp add -n sshyp add -n /notes/testNote
*follow the prompts
Syncing entries with the server: Manually syncing entries with the server:
sshyp sync sshyp sync
Removing an existing folder saved as ~/.password-pasture/social: Removing an existing folder saved as ~/.password-pasture/social:
sshyp shear sshyp shear social/
*when prompted for what to delete, since you are deleting a folder, be sure to enter your choice with a following "/", e.g. "social/" or "/social/"
.SH OPTIONS .SH OPTIONS
In order to quickly read an existing entry, just run "sshyp </entry name>". USAGE: sshyp [OPTION [FLAG] [<entry name>]] | [/<entry name>]
Options for other operations are as follows:
help/--help/-h bring up the help menu help/--help/-h bring up the help menu
version/-v display sshyp version info version/-v display sshyp version info
@@ -71,11 +64,9 @@ copy:
gen: gen:
update/-u generate a password for an existing entry update/-u generate a password for an existing entry
.SH LIMITATIONS .SH LIMITATIONS
The following limitations will be corrected in future updates: The following characters/character sequences are not supported in entry/folder titles:
Currently, folders (for sorting) are not replicated onto new clients (they need to be manually created before syncing). @ ^&* *&^
Currently, the entry list displays each entry with the file extension ".gpg" visible (which you do not type when reading, modifying, or creating an entry). This will be corrected when the new entry list is added.
.SH SETUP .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. 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.
@@ -92,7 +83,6 @@ Client setup:
Install sshyp Install sshyp
Run "sshyp tweak" and follow the prompts Run "sshyp tweak" and follow the prompts
Copy the generated public SSH key to the server using preferred method (manually adding to authorized_keys, ssh-copy-id, etc.) Copy the generated public SSH key to the server using preferred method (manually adding to authorized_keys, ssh-copy-id, etc.)
**this step will be automated in the future: if an entry library already exists on the server, make sure to manually recreate any folders that exist on the server in the ~/.password-pasture directory
If you already have entries on the server, sync them using "sshyp sync" If you already have entries on the server, sync them using "sshyp sync"
Done! Done!
+48
View File
@@ -0,0 +1,48 @@
#!/bin/python3
from os import path, remove, system, walk
from pathlib import Path
from shutil import move, rmtree
if __name__ == "__main__":
gpg_id = input('\nWhat is the ID of the GPG key you would like to use for re-encryption?\n\nID: ')
pasture = path.expanduser('~/.password-pasture')
if Path(f"{pasture}.new").exists():
rmtree(f"{pasture}.new")
if Path(f"{pasture}.old").exists():
rmtree(f"{pasture}.old")
if path.isdir(pasture):
for dirPath, dirNames, filenames in walk(pasture):
for filename in sorted(filenames):
Path(dirPath.replace(pasture, pasture + '.new')).mkdir(0o700, parents=True, exist_ok=True)
system(f"gpg -d '{dirPath}/{filename}' "
f"> '{dirPath.replace(pasture, pasture + '.new')}/{filename[:-4]}'")
_old_contents, _new_contents = \
open(f"{dirPath.replace(pasture, pasture + '.new')}/{filename[:-4]}", 'r').readlines(), ''
for _num in range(len(_old_contents)):
if _num == 0:
try:
_new_contents += _old_contents[1]
except IndexError:
_new_contents += '\n'
elif _num == 1:
_new_contents += _old_contents[0]
elif _num == 3:
if _old_contents[_num] == ' ' or _old_contents[_num] == '\n':
pass
else:
_new_contents += _old_contents[_num]
else:
_new_contents += _old_contents[_num]
for _num in reversed(range(len(_new_contents))):
if _new_contents[_num] == '\n' or _new_contents[_num] == '':
_new_contents = _new_contents[:-1]
else:
break
open(f"{dirPath.replace(pasture, pasture + '.new')}/{filename[:-4]}", 'w').writelines(_new_contents)
system(f"gpg -qr {str(gpg_id)} -e '{dirPath.replace(pasture, pasture + '.new')}/{filename[:-4]}'")
remove(f"{dirPath.replace(pasture, pasture + '.new')}/{filename[:-4]}")
else:
print(f"\nError: no entry folder ({pasture}) found\n")
move(pasture, f"{pasture}.old")
move(f"{pasture}.new", pasture)
+41
View File
@@ -0,0 +1,41 @@
#!/bin/python3
from os import path, remove, system, walk
from pathlib import Path
from shutil import move, rmtree
if __name__ == "__main__":
gpg_id = input('\nWhat is the ID of the GPG key you would like to use for re-encryption?\n\nID: ')
pasture = path.expanduser('~/.password-pasture')
if Path(f"{pasture}.new").exists():
rmtree(f"{pasture}.new")
if Path(f"{pasture}.old").exists():
rmtree(f"{pasture}.old")
if path.isdir(pasture):
for dirPath, dirNames, filenames in walk(pasture):
for filename in sorted(filenames):
Path(dirPath.replace(pasture, pasture + '.new')).mkdir(0o700, parents=True, exist_ok=True)
system(f"gpg -d '{dirPath}/{filename}' "
f"> '{dirPath.replace(pasture, pasture + '.new')}/{filename[:-4]}'")
_old_contents, _new_contents = \
open(f"{dirPath.replace(pasture, pasture + '.new')}/{filename[:-4]}", 'r').readlines(), ''
for _num in range(len(_old_contents)):
if _num == 3:
if _old_contents[_num] == ' ' or _old_contents[_num] == '\n':
pass
else:
_new_contents += _old_contents[_num]
else:
_new_contents += _old_contents[_num]
for _num in reversed(range(len(_new_contents))):
if _new_contents[_num] == '\n' or _new_contents[_num] == '':
_new_contents = _new_contents[:-1]
else:
break
open(f"{dirPath.replace(pasture, pasture + '.new')}/{filename[:-4]}", 'w').writelines(_new_contents)
system(f"gpg -qr {str(gpg_id)} -e '{dirPath.replace(pasture, pasture + '.new')}/{filename[:-4]}'")
remove(f"{dirPath.replace(pasture, pasture + '.new')}/{filename[:-4]}")
else:
print(f"\nError: no entry folder ({pasture}) found\n")
move(pasture, f"{pasture}.old")
move(f"{pasture}.new", pasture)
+54 -10
View File
@@ -1,15 +1,14 @@
#!/bin/bash #!/bin/bash
# This script packages sshyp (from source) for various Linux environments. # This script packages sshyp (from source) for various UNIX environments.
# NOTE It is recommended to instead use the latest officially packaged and tagged release. # NOTE It is recommended to instead use the latest officially packaged and tagged release.
# NOTE If using Arch, it is recommended to install from the AUR or from the PKGBUILD attatched to the latest official release. # NOTE If using Arch, it is recommended to install from the AUR or from the PKGBUILD attatched to the latest official release.
# NOTE As of release fr4, sshync will become a separate package and thus a dependency for sshyp. sshync is automatically installed with the PKGBUILD version.
echo -e '\nOptions (please enter the number only):' echo -e '\nOptions (please enter the number only):'
echo -e '\nDistribution Packages:\n\n1. Debian\n2. Termux\n3. Generic (used for PKGBUILD/APKBUILD)' echo -e '\nDistribution Packages:\n\n1. Haiku\n2. Debian\n3. Termux\n4. Generic (used for PKGBUILD/APKBUILD)'
echo -e '\nBuild Scripts:\n\n4. Alpine Linux (APKBUILD)\n5. Arch Linux (PKGBUILD, also builds local generic package)' echo -e '\nBuild Scripts:\n\n5. Alpine Linux (APKBUILD)\n6. Arch Linux (PKGBUILD, also builds local generic package)'
echo -e '\nOther:\n\n6. All (generates all distribution packages and build scripts)\n' echo -e '\nOther:\n\n7. All (generates all distribution packages (excluding Haiku, as this must be done on Haiku) and build scripts)\n'
read -n 1 -r -p "Distribution: " distro 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' 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'
@@ -18,7 +17,7 @@ read -r -p "Version number: " version
echo -e '\nThe value entered in this field will only affect the revision number for build scripts.\n' echo -e '\nThe value entered in this field will only affect the revision number for build scripts.\n'
read -r -p "Revision number: " revision read -r -p "Revision number: " revision
if [ "$distro" == "4" ] || [ "$distro" == "5" ] || [ "$distro" == "6" ]; then if [ "$distro" == "5" ] || [ "$distro" == "6" ] || [ "$distro" == "7" ]; then
echo -e '\nOptions (please enter the number only):' echo -e '\nOptions (please enter the number only):'
echo -e '\n1. GitHub Release Tag\n2. Local\n' echo -e '\n1. GitHub Release Tag\n2. Local\n'
read -r -p "Source (for build scripts): " source read -r -p "Source (for build scripts): " source
@@ -32,7 +31,52 @@ fi
mkdir -p packages mkdir -p packages
if [ "$distro" == "1" ] || [ "$distro" == "6" ]; then if [ "$distro" == "1" ]; then
echo -e '\nPackaging for Haiku...\n'
mkdir -p packages/haikutemp/documentation/man/man1
mkdir -p packages/haikutemp/documentation/packages/sshyp
echo "name sshyp
version "$version"-"$revision"
architecture any
summary \"A light-weight, self-hosted, synchronized password manager\"
description \"sshyp is the only password-store compatible CLI password manager available for Haiku - it is also available on Linux/Android (via Termux) so that you can sync your entries across all of your devices.\"
packager \"Randall Winkhart <idgr at tutanota dot com>\"
vendor \"Randall Winkhart\"
licenses {
\"GNU GPL v3\"
}
copyrights {
\"2021-2022 Randall Winkhart\"
}
provides {
sshyp = "$version"
cmd:sshyp
}
requires {
gnupg
openssh
python3
nano
}
urls {
\"https://github.com/rwinkhart/sshyp\"
}
" > packages/haikutemp/.PackageInfo
cp -r bin packages/haikutemp/
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
package create -b sshyp-"$version"-"$revision"_all.hpkg
package add sshyp-"$version"-"$revision"_all.hpkg bin documentation
cd ../..
mv packages/haikutemp/sshyp-"$version"-"$revision"_all.hpkg packages/
rm -rf packages/haikutemp
echo -e "\nHaiku packaging complete.\n"
fi
if [ "$distro" == "2" ] || [ "$distro" == "7" ]; then
echo -e '\nPackaging for Debian...\n' echo -e '\nPackaging for Debian...\n'
mkdir -p packages/debiantemp/sshyp_"$version"-"$revision"_all/{DEBIAN,usr} mkdir -p packages/debiantemp/sshyp_"$version"-"$revision"_all/{DEBIAN,usr}
mkdir -p packages/debiantemp/sshyp_"$version"-"$revision"_all/usr/share/man/man1 mkdir -p packages/debiantemp/sshyp_"$version"-"$revision"_all/usr/share/man/man1
@@ -56,7 +100,7 @@ Installed-Size: 35
echo -e "\nDebian packaging complete.\n" echo -e "\nDebian packaging complete.\n"
fi fi
if [ "$distro" == "2" ] || [ "$distro" == "6" ]; then if [ "$distro" == "3" ] || [ "$distro" == "7" ]; then
echo -e '\nPackaging for Termux...\n' echo -e '\nPackaging for Termux...\n'
mkdir -p packages/termuxtemp/sshyp_"$version"-"$revision"_all_termux/{data,DEBIAN} mkdir -p packages/termuxtemp/sshyp_"$version"-"$revision"_all_termux/{data,DEBIAN}
mkdir -p packages/termuxtemp/sshyp_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/share/man/man1 mkdir -p packages/termuxtemp/sshyp_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/share/man/man1
@@ -80,7 +124,7 @@ Installed-Size: 35
echo -e "\nTermux packaging complete.\n" echo -e "\nTermux packaging complete.\n"
fi fi
if [ "$distro" == "3" ] || [ "$distro" == "5" ] || [ "$distro" == "6" ]; then if [ "$distro" == "4" ] || [ "$distro" == "6" ] || [ "$distro" == "7" ]; then
echo -e '\nPackaging as generic...\n' echo -e '\nPackaging as generic...\n'
mkdir -p packages/archtemp/usr mkdir -p packages/archtemp/usr
cp -r bin packages/archtemp/usr/ cp -r bin packages/archtemp/usr/
@@ -95,7 +139,7 @@ if [ "$distro" == "3" ] || [ "$distro" == "5" ] || [ "$distro" == "6" ]; then
echo -e "\nGeneric packaging complete.\n" echo -e "\nGeneric packaging complete.\n"
fi fi
if [ "$distro" == "5" ] || [ "$distro" == "6" ]; then if [ "$distro" == "6" ] || [ "$distro" == "7" ]; then
echo -e '\nGenerating PKGBUILD...' echo -e '\nGenerating PKGBUILD...'
echo "# Maintainer: Randall Winkhart <idgr at tutanota dot com> echo "# Maintainer: Randall Winkhart <idgr at tutanota dot com>
+23 -41
View File
@@ -1,51 +1,34 @@
sshyp 2022.02.16.fr4 sshyp v1.0.0
The High-Tech Shears Update The Polished Trotters Update
This release adds in a major feature: multi-client deletion (details below) This release brings 'sshyp' in line with the original goals of the project.
As such, it is being crowned "v1.0.0", and is considered a stable base for future expansions.
Note: This release requires the configuration ("sshyp tweak") to be re-done, as a device name now must be set (for multi-client deletion).
Features: Features:
- added multi-client deletion - sshyp has been modified to run on Haiku, an open-source re-implementation of BeOS
^ this means that when an entry is deleted from one client (and thus the server), if another client that has a copy of the deleted entry tries to sync, instead of re-uploading the deleted entry, it will now also have its entry deleted. - the entry format has been changed to support entries created in pass/password-store
^ the current implementation requires a new Python script in addition to sshyp and sshync ^ this change requires existing entry libraries to be converted - see the sshyp v1.0.0 release page on GitHub for a conversion script
- majorly overhauled visual experience for sshyp
^ this includes a new entry list, a new entry readout, coloured output, and more!
- entries can now optionally be passed as arguments, directly
^ as opposed to the user being prompted for an entry name after running a command
Changes: Changes:
- fixed an issue where sshyp would use the wrong SSH key when trying to delete entries from or create folders on the server - corrected outdated liscensing and copyright information
^ this is a fix that was recently applied to sshync - turns out I made the same mistake in sshyp - 'if __name__=="__main__":' convention is now used
- deleting folders should now work properly with "sshyp shear" if the directory is entered with a following "/" - help screens have been overhauled
- improved password generator to guarantee more secure results
&& - fixed errors when trying to sync with a multi-word entry on the server
- sshync now uses 'scp' (via the sftp protocal), rather than 'sftp'
Planned for next major update (fr5, The sshyp Doing a Split Update): - rename function now properly allows for renaming folders
- renaming an entry/folder will now properly delete the old entry/folder from the server
sshyp: - entry previews at the time of entry creation/editing no longer require entering the gpg password more times than necessary
** sshyp has recieved a major visual overhaul - the clipboard now automatically clears after 30 seconds
^ this includes the new file list and thematic text art - sshyp server no longer needs to create copies of its libraries in ~/
- ...and lots of minor (under the hood) changes!
sshync:
** sshync has been split into a separate package and now has standalone functionality as a backup/syncing utility
^ more details will be available in the sshync repo, once it is created
&&
Planned for next, next major update (fr6, The Farmer Shearing the sshyp Update):
** when syncing, a check is made to see if any folders are not on all devices - if the check comes back true, it is corrected (in sshyp)
** imporove password generator to guarantee more secure results
** enforce permissions on the server-side
** allow for copying entire notes (not just first line)
** ability to pass entries as arguments for more functions
&&
Backlog:
** create separate gui frontend targetting mobile and desktop Linux
** auto-completion on tab (currently unsure of best way to make this work)
<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
@@ -57,4 +40,3 @@ Legend:
** = feature/change not yet finished/implemented ** = feature/change not yet finished/implemented
- = feature/change implemented and pushed upstream - = feature/change implemented and pushed upstream
^ = note regarding the above feature/change ^ = note regarding the above feature/change
&& = visual separater for patch notes for different planned major version releases
+2 -2
View File
@@ -1,5 +1,5 @@
sshyp is a FOSS password manager that takes advantage of rsync sshyp is a FOSS password manager that uses an sftp-based syncing back-end.
Copyright (C) 2021 Randall Winkhart idgr@tutanota.com Copyright (C) 2022 Randall Winkhart idgr@tutanota.com
This program is free software: you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by