Compare commits

...
36 Commits
Author SHA1 Message Date
RandyTheSilly 921630f5a8 Re-organized badges
Former-commit-id: b6055b13fc6cdb1b629ea4a76e3f5997fbbbf33f [formerly fdc014c44a]
Former-commit-id: 3c86294a4149f4395a2314e58a8c0d1aef4a43fb
2022-12-14 02:52:23 -05:00
RandyTheSilly 627c6a1a14 Update documentation for release v1.3.0
Former-commit-id: ac89c761d964360d80863c926f908d937ed00865 [formerly 697f7a870d]
Former-commit-id: fde0008b65ef6135476a448dd1cc768a7e511cb8
2022-12-14 02:40:13 -05:00
RandyTheSilly a9f4435493 Replaced use of system-provided chmod and find with os.chmod and os.walk, respectively
Former-commit-id: 89be833d3e15aaf00578a093ce5b5efb8c3b84f7 [formerly bad92878e4]
Former-commit-id: 1871adcc80cd91666325fe0a3db3771c9256357c
2022-12-14 01:05:22 -05:00
RandyTheSilly 7d639ecca9 Use getpass() for quick-unlock password input
Former-commit-id: b7312096d7b58ddea540f08064ea536fb6d0543c [formerly b4fb37d877]
Former-commit-id: 672c2e53a6827119a85a41e435508453f693009e
2022-12-11 18:21:31 -05:00
RandyTheSilly eefe5e39b5 Fixed deletions occuring after the local index is generated, causing sshync to believe sheared files still exist on the client
Former-commit-id: 0f303028224689cb7b5e290d28ad30d7ea0a0776 [formerly 4ebbf7847d]
Former-commit-id: 348fa6dcc8b6f35c102c9cd9c3912d161bc200e7
2022-12-07 22:29:15 -05:00
RandyTheSilly c28b836c2a Updated packaging script to accommodate the removal of sshypRemote.py
Former-commit-id: ecdc146022c96c463d4ce0bbb3601eb2f128b582 [formerly af44f244db]
Former-commit-id: 42e50a6d827987551163a386b78c487502defe8d
2022-12-07 20:43:56 -05:00
RandyTheSilly b53370a3e2 Strip entry/folder names after user input to ensure compatibility with sshync
Former-commit-id: f07a48fc2bde0d651199d62cb0f35a1c18f51f8e [formerly 9d8eca3a2b]
Former-commit-id: 438ac436902369f1af4dc8dae5abc38d7e6dabaf
2022-12-07 20:27:39 -05:00
RandyTheSilly 9776961f2a Assigned exit code 6 to "server connection error"
Former-commit-id: 34c2312b779b7c0f6e6dfcd1e9b4d7e700229142 [formerly b24d1df721]
Former-commit-id: 72c0e4cef04abd78bffb3731434c55cf9998eb7b
2022-12-07 15:55:01 -05:00
RandyTheSilly 1a35aaf33b Combined all list fetching functions into one to drastically reduce the number of SSH connections (speeds up sshync exponentially)
Former-commit-id: 11290b3915a2f3d92696e0953997c3fb293ec7e3 [formerly 6080726dc6]
Former-commit-id: 3f799d1c8e2d3cd694cad90ba9f4444bb2673934
2022-12-07 15:46:33 -05:00
RandyTheSilly 66b15ac22c Organized functions in sshync.py by use method
Former-commit-id: 1bbcac88ffe5c4d08aac1f34c1a40ce0fbff092e [formerly a49334482c]
Former-commit-id: ba2ee6fa69228ef2fe52560f4aa22c39ec69c9ab
2022-12-05 23:21:02 -05:00
RandyTheSilly 56db2e65f8 Moved all syncing functionality into sshync.py, removed sshypRemote.py
Former-commit-id: bf7833a21db35a7d33fdd89ee8a41775fd2f55c0 [formerly 56ea8d0cf9]
Former-commit-id: 820f85f9b0efc8a33ebf8a33f751e8f130fc8c34
2022-12-05 12:13:28 -05:00
RandyTheSilly 1be67d404b Call specifically 'python3' over ssh, as some environments do not have a binary/symlink named 'python', or 'python' may refer to python2
Former-commit-id: db4e264c648a05a9d0293873c5ffae6173e4f8a0 [formerly 6f016bfb0a]
Former-commit-id: f4635300ddda1927fa89b63edf0dc0be7729abbf
2022-12-05 09:36:37 -05:00
RandyTheSilly a279819260 Replaced use of system-provided 'ls' with 'os.listdir'
Former-commit-id: cbd6302ec15624381303d1181861d3c2792475a6 [formerly 31a4c7b8e7]
Former-commit-id: acd4d9db5d557793aca4662e48ca88320b7a3bfa
2022-12-04 18:57:23 -05:00
RandyTheSilly 578f7dee14 Replaced use of system-provided 'touch' with pathlib's Path.touch, added 'mode=' when specifying permissions
Former-commit-id: 7ca6d3e2ef3ff494932a849d9ea576218ec5e054 [formerly 45e2b60e10]
Former-commit-id: b477cbd7b3771aa8a513d4ef481e725c59b4bdf4
2022-12-04 18:33:41 -05:00
RandyTheSilly 5bec4fdaa9 Detect Wayland sessions by checking if $WAYLAND_DISPLAY is set
Former-commit-id: 584427e2b5f0d8a1cad1c1fa30b5024d976b9a14 [formerly d7987a1bc0]
Former-commit-id: c79d54b2d7fb8ff2f1283c388ad5aa1c16a7dcd1
2022-12-04 17:45:50 -05:00
RandyTheSilly 5f903966da Re-ordered clipboard support to match support priorities (Wayland first)
Former-commit-id: f14120402a6fde4a3d3124b7965e1ec5de70d8b4 [formerly 952fdd160b]
Former-commit-id: 1d96d2fbfda35b617ec7cfef1530bcba1d650601
2022-12-04 17:38:10 -05:00
RandyTheSilly cfd551ef7c Replace occasional use of system-provided mkdir with pathlib's Path.mkdir
Former-commit-id: 72c92e8f07ad9f3933c2593f426e2c05b194c3b3 [formerly c8c394e550]
Former-commit-id: d6eb5469dfd16a538da5fff5dcd6525aa3995234
2022-12-04 15:31:31 -05:00
RandyTheSilly 4d22b5b5a8 Removed all remaining instances of subprocess.run being called with shell=True
Former-commit-id: c6d663cf3af2e5a06f13e5cb3571d668880f6d6e [formerly db4351469e]
Former-commit-id: cba35421c41474e5daf5315f34b1f6d86380bc24
2022-11-28 14:17:26 -05:00
RandyTheSilly 46d5f8d515 Replace remaining uses of system()
Former-commit-id: 5a8ca97389b8d37c8050ba23d1e455357f5be0b1 [formerly e8703300fd]
Former-commit-id: 81b4d2783d5b09cc31515ab22f0f42e5ed55218b
2022-11-28 13:38:17 -05:00
RandyTheSilly 02ee2a5c0e Allow Haiku to use default pinentry, thanks to haikuports/haikuports#7457
Former-commit-id: 70edaa9d4c2a88d483d1247dc5682a30f0d593e5 [formerly 322db7ac54]
Former-commit-id: 6677ac5f2ae91f5b7a8c6656432ef962013592ff
2022-11-28 12:52:54 -05:00
RandyTheSilly fb1c3844a8 Use XDG_SESSION_TYPE to check for Wayland, rather than WAYLAND_DISPLAY
Former-commit-id: 4598a3038d49df81426dfcf2a29a366ea6bc4c74 [formerly cc81f219fa]
Former-commit-id: 4c8b1ddc2d9f7cee44007de0e2ae4cbea882a20e
2022-11-26 23:57:12 -05:00
RandyTheSilly 213ca41582 Renaming no longer throws an error while not in offline mode
Former-commit-id: e69e157a951cfc4151e3e511585c04a24b5a08ee [formerly bf20b83738]
Former-commit-id: dd90cb9f0518b9071b170a6b549cb71a352cd249
2022-11-26 15:18:47 -05:00
RandyTheSilly b8dc5c4f89 Move some rarely used import statements closer to where they are needed
Former-commit-id: a4785c178b5da044d1cf2ec2ecb2868ed8e3e877 [formerly 74f9f616f3]
Former-commit-id: c7c06f402627cc0169ed9e654086e89123639902
2022-11-26 14:05:19 -05:00
RandyTheSilly 574637392a Reduced code used for clipboard support
Former-commit-id: 2a7ac0d078526e60f742f8bdee1689f9f27df3f2 [formerly daf3f0d7bf]
Former-commit-id: d4d0683c63e19c3b9fdc33d02c105244e037035c
2022-11-26 13:53:47 -05:00
RandyTheSilly c6f31da6d4 Replace all currently replaceable instances of system() or run(X, shell=True) with run(X, shell=False) for security and compatibility
Former-commit-id: 8e316228ca46dbe9e6811625281c19a1118ff650 [formerly dc218025a7]
Former-commit-id: 7d19e68ccbd2b844ab00df8db69bbf08b9a1884e
2022-11-26 13:19:09 -05:00
RandyTheSilly 0f4c57bec3 Attempt to create ~/.ssh regardless of OS
Former-commit-id: 424cb44c13e9512d57cf6e04215cf975421e1336 [formerly 3a63966827]
Former-commit-id: b5847a53398576802f71ecddf005606de6d97481
2022-11-25 21:40:23 -05:00
RandyTheSilly 51b1b8c6ec Use os.symlink in place of shell commands
Former-commit-id: 3c62f4bb691a7c8d4b7419697c0e81e5826de736 [formerly 3ad2068b11]
Former-commit-id: 6fd9419e1904efabae07af45293da4f56c3b3188
2022-11-25 21:32:18 -05:00
RandyTheSilly 5f0a40efc0 Removed exit code 6 from man page, as it no longer has reason to exist in sshyp
Former-commit-id: 56f1761cbc55f7a348db132dfd7a8714bfe2fac7 [formerly 9b9f49a344]
Former-commit-id: 205e9dbbff27b8732024a8e602786afc104f050c
2022-11-25 21:17:41 -05:00
RandyTheSilly 05c034652f Replaced database downloads in sync() with newer output capture logic from sshync - much faster syncing
Former-commit-id: f6a9dce34263b3385440e6c166f9880f4781b5d5 [formerly 8276b9164e]
Former-commit-id: 850bf5889c2cf401ad9e91da5512a7455a353e6d
2022-11-25 21:11:07 -05:00
RandyTheSilly 1d185b1723 Removed useless pass statement in sync()
Former-commit-id: 12eebb98186597f164a3cc38306f67758273e506 [formerly dce6ad4390]
Former-commit-id: 4275468f89557716a8eec72183caaf2eccd5aad4
2022-11-25 19:37:13 -05:00
RandyTheSilly bca2af0ed8 pass_gen no longer recurses when the user gives invalid input
Former-commit-id: c9756093cd861911b7f5dc05a71fc5a8b858c17e [formerly b10c111640]
Former-commit-id: c8b7974ec39c4e114578314814c5c2a982339736
2022-11-25 19:23:14 -05:00
RandyTheSilly d301cf6298 Import os.path.expanduser directly to remove lots of repeated text
Former-commit-id: 247c04a8aa9720fd0a06a128d292a21cca2cf08c [formerly 3a96ef29dc]
Former-commit-id: 3f97f7d5199c477f311789a6c5c3f38c4624d10e
2022-11-25 18:45:06 -05:00
RandyTheSilly 89c9a03cc1 Use subprocess.run without shell=True where easily applicable, redirect unused captured output to DEVNULL
Former-commit-id: bae14d658f4e1fa04f723e8545bc35d69e0c4cbb [formerly 3a85f06ffe]
Former-commit-id: 7b7b48c870b9a6d4646d9be3d208ea792e665399
2022-11-25 18:11:38 -05:00
RandyTheSilly 3392e3ceea Use ASCII 30 and 31 as separators, removes limitations on "@", "^&*", and "*&^".
Former-commit-id: cd08e1adde9f810e0099338722b617357055c422 [formerly 289d12f45a]
Former-commit-id: 7f70d015e3afe2101aba2d1d21001101db0de81e
2022-11-25 12:00:51 -05:00
RandyTheSilly 176ee623cf Replaced sshync mod time/title retrieval logic with much more efficient and compatible logic
Former-commit-id: a9107238fd80398b570ca04ac36e212bcb3c0ddc [formerly ae5172a6c8]
Former-commit-id: 4984678e5544d0594bbcbe10b23100427ca2e8b8
2022-11-24 19:40:04 -05:00
RandyTheSilly 42abb7b674 Corrected Fedora packaging
Former-commit-id: 5fc5021f68b98bcb71df7794f2c7961445926c38 [formerly d1c968ed12]
Former-commit-id: 7386503874acef51bb1f02b02eab4637b94dff60
2022-11-06 14:00:51 -05:00
8 changed files with 376 additions and 371 deletions
+6 -3
View File
@@ -1,5 +1,9 @@
![sshyp](https://github.com/rwinkhart/sshyp-labs/blob/main/extra/artwork/sshyp-banner.png)
[![release](https://img.shields.io/github/v/release/rwinkhart/sshyp)](https://github.com/rwinkhart/sshyp/releases)
![python](https://img.shields.io/badge/python-3.7--3.11-yellow)
[![downloads](https://img.shields.io/github/downloads/rwinkhart/sshyp/total)](https://github.com/rwinkhart/sshyp/releases)
[![CodeQL](https://github.com/rwinkhart/sshyp/actions/workflows/codeql-analysis.yml/badge.svg?branch=main)](https://github.com/rwinkhart/sshyp/actions/workflows/codeql-analysis.yml)
pronounced as: 'sheep', 'shēp'
@@ -91,9 +95,8 @@ man sshyp
# Roadmap
Short-term Goals:
- create minimal GUI app (Linux x86_64, Linux aarch64) - being done as an [extension](https://github.com/rwinkhart/sshyp-labs)
- significant optimizations
- vaious bug fixes
- a minimal GUI app - being made as an [extension](https://github.com/rwinkhart/sshyp-labs)
- improved extension integration (allow extensions to add new arguments)
Long-term Goals:
+48
View File
@@ -1,3 +1,51 @@
sshyp v1.3.0
the serious shepherd update
this release ties up many of sshyp's loose ends where there was
room for major performance, compatibility, and security improvements
compatibility-breaking changes:
- due to a near full re-write of the syncing functionality, all clients and servers
must be updated to this release (v1.3.0 is not backwards compatible with any prior release)
- it is recommended to either delete the contents of ~/.config/sshyp/deleted (on the server-side)
or sync all of your clients before updating
^ old entries in this folder will throw errors with v1.3.0
user-facing features:
- none - all changes were under-the-hood - the user experience should be
exactly the same as v1.2.0 - just faster, less buggy, and more secure
major fixes/optimizations:
- a near full re-write of the syncing functionality
^ all syncing logic has been moved into sshync.py (from sshyp.py and sshypRemote.py)
^ in my setup, a dry, local "sshyp sync" went from 2.00+ seconds (v1.2.0) to 0.36 seconds (v1.3.0)
^ the performance improvements are even greater when syncing from outside your local network
- the following character sequences will no longer break the syncing logic: "@", "^&*", and "*&^"
^ ASCII separator characters 29-31 are now used, instead
- os.system has been replaced with subprocess.run in all cases, shell=True is no longer used with subprocess.run
^ this protects against shell escape attacks and potentially makes sshyp more compatible with some environments
- replaced shell commands with python built-in library functions where applicable
^ this brings speed and compatibility improvements
- sshyp should no longer incorrectly assume an X11 environment when Wayland is in use
^ this fixes clipboard support in some Wayland environments, such as Sway (Plasma/Gnome/Phosh were unaffected)
- sshyp now uses the default pinentry on Haiku thanks to haikuports/haikuports#7457
^ this brings the Haiku port in-line with the other sshyp packages in terms of security
- "python3" is now called over ssh, rather than "python"
^ some environments do not have a "python" symlink, or it links to "python2" - changing this increases compatibility
- fixed an issue from v1.2.0 where renaming threw an error if not in offline mode
other notable changes:
- quick-unlock password input is now hidden while the user is typing
^ user input is now invisible to prevent snooping
- lots of smaller optimizations not listed here
<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
sshyp v1.2.0
the brisk bahh update
+2 -6
View File
@@ -1,4 +1,4 @@
.TH sshyp 1 "06 November 2022" "v1.2.0" "sshyp man page"
.TH sshyp 1 "14 December 2022" "v1.3.0" "sshyp man page"
.SH NAME
sshyp \- A very simple self-hosted, synchronized password manager for UNIX(-like) systems. Alternative to (and compatible with) pass/password-store.
.SH SYNOPSIS
@@ -88,10 +88,6 @@ whitelist:
list/-l view all registered device ids and their quick-unlock whitelist status
add whitelist a device id for quick-unlock
delete/del remove a device id from the quick-unlock whitelist
.SH LIMITATIONS
The following characters/character sequences are not supported in entry/folder titles:
@ ^&* *&^
.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.
@@ -127,6 +123,6 @@ Please note that the server setup intentionally does not allow the reading of en
5 - decryption/encryption error
6 - server data retrieval error
6 - server connection error
.SH AUTHOR
Randall Winkhart (https://github.com/rwinkhart)
+116 -52
View File
@@ -1,45 +1,100 @@
#!/usr/bin/env python3
# external modules
from os import path, system, walk
from os import listdir, remove, walk
from os.path import expanduser, getmtime, join
from subprocess import CalledProcessError, PIPE, run
from sys import exit as s_exit
# utility functions
# REMOTE
def get_titles_mods(_directory, _destination, _user_data): # fetches and returns lists of titles and their mod times
from pathlib import Path
def remote_list_gen(_client_device_name, _remote_dir): # prints all necessary remote data to stdout
# deletions
for _file in listdir(expanduser('~/.config/sshyp/deleted')):
_file_path, _sep, _device = _file.partition('\x1f')
_file_path = _file_path.replace('\x1e', '/')
if _device == _client_device_name:
print(_file_path)
try:
remove(f"{expanduser('~/.config/sshyp/deleted/')}{_file}")
except FileNotFoundError:
pass
print('\x1d')
# folders
for _root, _directories, _files in walk(expanduser('~/.local/share/sshyp')):
for _dir in _directories:
print(f"{_root.replace(expanduser('~'), '')}/{_dir}")
print('\x1d')
get_local_data(_remote_dir, 'server') # titles and mod times
# HYBRID
def delete(_file_path, _target_database): # deletes a file or folder and/or marks it for deletion upon syncing
from shutil import rmtree
try:
if _file_path.endswith('/'):
rmtree(f"{expanduser('~/.local/share/sshyp/')}{_file_path}")
else:
remove(f"{expanduser('~/.local/share/sshyp/')}{_file_path}.gpg")
except FileNotFoundError:
print(f"location does not exist {_target_database}")
if _target_database == 'remotely':
for _device_name in listdir(expanduser('~/.config/sshyp/devices')):
open(expanduser('~/.config/sshyp/deleted/') + _file_path.replace('/', '\x1e') + '\x1f' + _device_name, 'w')
def get_local_data(_directory, _device): # retrieves and returns titles and mod times from the local device
_title_list, _mod_list = [], []
Path(path.expanduser('~/.config/sshync')).mkdir(0o700, parents=True, exist_ok=True) # create config directory
# local fetching
if _destination == 'l':
open(expanduser('~/.config/sshync/database'), 'w').write('') # blanks out database file
for _root, _directories, _files in walk(_directory):
for _filename in _files:
_title_list.append(join(_root.replace(_directory, '', 1), _filename))
_mod_list.append(int(getmtime(join(_root, _filename))))
for _root, _directories, _files in walk(_directory):
for _filename in _files:
_title_list.append(join(_root.replace(_directory, '', 1), _filename))
_mod_list.append(int(getmtime(join(_root, _filename))))
if _device == 'server':
for _title in _title_list:
open(expanduser('~/.config/sshync/database'), 'a').write(str(_title) + '\n')
open(expanduser('~/.config/sshync/database'), 'a').write('^&*\n')
for _mod in _mod_list:
open(expanduser('~/.config/sshync/database'), 'a').write(str(_mod) + '\n')
# remote fetching
if _destination == 'r':
system(f"ssh -i '{_user_data[5]}' -p {_user_data[2]} {_user_data[0]}@{_user_data[1]} \"cd /lib/sshyp; python -c"
f" 'import sshync; sshync.get_titles_mods(\"'\"{_user_data[4]}\"'\", \"'\"l\"'\", \"'\"{_user_data}"
f"\"'\")'\"")
system(f"scp -pqs -P {_user_data[2]} -i '{_user_data[5]}' {_user_data[0]}@{_user_data[1]}:"
f"'/home/{_user_data[0]}/.config/sshync/database' '{expanduser('~/.config/sshync/')}'")
_titles, _sep, _mods = '*&^'.join(open(expanduser('~/.config/sshync/database')).readlines()).replace('\n', '')\
.partition('^&*')
_title_list, _mod_list = _titles.split('*&^')[:-1], _mods.split('*&^')[1:]
print(_title.rstrip())
for _time in _mod_list:
print(_time)
return _title_list, _mod_list
# LOCAL
def remote_list_fetch(_user_data): # captures and returns all necessary data from the remote server
try:
_remote_data = run(['ssh', '-i', _user_data[5], '-p', _user_data[2], f"{_user_data[0]}@{_user_data[1]}",
f'cd /lib/sshyp; python3 -c \'from sshync import remote_list_gen; remote_list_gen'
f'("{_user_data[6]}", "{_user_data[4]}")\''], stdout=PIPE, text=True, check=True
).stdout.split('\x1d')
except CalledProcessError:
print('\n\u001b[38;5;9merror: failed to connect to the remote server\u001b[0m\n')
_remote_data = ''
s_exit(6)
_deletion_database = _remote_data[0].strip().split('\n')
_folder_database = _remote_data[1].strip().split('\n')
_titles_mods = _remote_data[2].strip().split('\n')
return _deletion_database, _folder_database, _titles_mods[:len(_titles_mods)//2], \
_titles_mods[len(_titles_mods)//2:]
def deletion_sync(_deletion_database, _silent): # checks for and acts upon files and folders marked for deletion
for _file in _deletion_database:
if _file != '':
if _silent != 1:
print(f"\u001b[38;5;208m{_file}\u001b[0m has been sheared, removing...")
delete(_file, 'locally')
def folder_sync(_folder_database): # creates matches of remote folders on the local client
from pathlib import Path
for _folder in _folder_database:
if _folder != '' and not Path(f"{expanduser('~')}{_folder}").is_dir():
print(f"\u001b[38;5;2m{_folder.replace('/.local/share/sshyp/', '')}/\u001b[0m does not exist locally, "
f"creating...")
Path(f"{expanduser('~')}{_folder}").mkdir(mode=0o700, parents=True, exist_ok=True)
def sort_titles_mods(_list_1, _list_2): # creates and returns two lists (of titles and mod times) generated by sorting
# information from two different, provided lists
# information from two provided 2D lists
_title_list_2_sorted, _mod_list_2_sorted = [], []
# title sorting
for _title in _list_1[0]:
@@ -62,43 +117,52 @@ def get_profile(_profile_dir): # returns a list of data read from a sshync job
try:
_profile_data = open(_profile_dir).readlines()
except (FileNotFoundError, IndexError):
print('\n\u001b[38;5;9merror: the profile does not exist or is corrupted.\u001b[0m\n')
print('\n\u001b[38;5;9merror: the profile does not exist or is corrupted\u001b[0m\n')
_profile_data = None
s_exit(3)
# extract data from profile
_user = _profile_data[0].rstrip()
_ip = _profile_data[1].rstrip()
_port = _profile_data[2].rstrip()
_local_dir = _profile_data[3].rstrip()
_remote_dir = _profile_data[4].rstrip()
_identity = _profile_data[5].rstrip()
return _user, _ip, _port, _local_dir, _remote_dir, _identity
_client_device_id = listdir(expanduser('~/.config/sshyp/devices'))[0].rstrip()
return _user, _ip, _port, _local_dir, _remote_dir, _identity, _client_device_id
def run_profile(_profile_dir): # runs a sshync job profile
_user_data = get_profile(_profile_dir)
_remote_titles_mods_saver = get_titles_mods(_user_data[4], 'r', _user_data) # saved to prevent re-walking directory
_index_l = sort_titles_mods(_remote_titles_mods_saver, get_titles_mods(_user_data[3], 'l', _user_data))
_index_r = sort_titles_mods(_index_l, _remote_titles_mods_saver)
def run_profile(_profile_dir, _silent): # runs a sshync job profile
_user_data = get_profile(_profile_dir) # import profile data
# fetch remote lists
_deletion_database, _folder_database, _remote_titles, _remote_mods = remote_list_fetch(_user_data)
_remote_titles_mods = (_remote_titles, _remote_mods)
# sync deletions and folders
deletion_sync(_deletion_database, _silent)
folder_sync(_folder_database)
# sort titles and mods
_index_local = sort_titles_mods(_remote_titles_mods, get_local_data(_user_data[3], 'client'))
_index_remote = sort_titles_mods(_index_local, _remote_titles_mods)
# sync new and updated files
_i = -1
for _title in _index_l[0]:
for _title in _index_local[0]:
_i += 1
if _title in _index_r[0]:
if _title in _index_remote[0]:
# compare mod times and sync
if int(_index_l[1][_i]) > int(_index_r[1][_i]):
if int(_index_local[1][_i]) > int(_index_remote[1][_i]):
print(f"\u001b[38;5;4m{_title[:-4]}\u001b[0m is newer locally, uploading...")
system(f"scp -pqs -P {_user_data[2]} -i '{_user_data[5]}' '{_user_data[3]}{_title}' "
f"'{_user_data[0]}@{_user_data[1]}:{_user_data[4]}{'/'.join(_title.split('/')[:-1]) + '/'}'")
elif int(_index_l[1][_i]) < int(_index_r[1][_i]):
run(['scp', '-pqs', '-P', _user_data[2], '-i', _user_data[5], _user_data[3] + _title,
f"{_user_data[0]}@{_user_data[1]}:{_user_data[4]}{'/'.join(_title.split('/')[:-1]) + '/'}"])
elif int(_index_local[1][_i]) < int(_index_remote[1][_i]):
print(f"\u001b[38;5;2m{_title[:-4]}\u001b[0m is newer remotely, downloading...")
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]) + '/'}'")
run(['scp', '-pqs', '-P', _user_data[2], '-i', _user_data[5],
f"{_user_data[0]}@{_user_data[1]}:{_user_data[4]}{_title}",
f"{_user_data[3]}{'/'.join(_title.split('/')[:-1]) + '/'}"])
else:
print(f"\u001b[38;5;4m{_title[:-4]}\u001b[0m is not on remote server, uploading...")
system(f"scp -pqs -P {_user_data[2]} -i '{_user_data[5]}' '{_user_data[3]}{_title}' "
f"'{_user_data[0]}@{_user_data[1]}:{_user_data[4]}{'/'.join(_title.split('/')[:-1]) + '/'}'")
for _title in _index_r[0]:
if _title not in _index_l[0]:
run(['scp', '-pqs', '-P', _user_data[2], '-i', _user_data[5], _user_data[3] + _title,
f"{_user_data[0]}@{_user_data[1]}:{_user_data[4]}{'/'.join(_title.split('/')[:-1]) + '/'}"])
for _title in _index_remote[0]:
if _title not in _index_local[0]:
print(f"\u001b[38;5;2m{_title[:-4]}\u001b[0m is not in local directory, downloading...")
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]) + '/'}'")
run(['scp', '-pqs', '-P', _user_data[2], '-i', _user_data[5],
f"{_user_data[0]}@{_user_data[1]}:{_user_data[4]}{_title}",
f"{_user_data[3]}{'/'.join(_title.split('/')[:-1]) + '/'}"])
+169 -224
View File
@@ -1,19 +1,17 @@
#!/usr/bin/env python3
# external modules
from os import environ, listdir, path, remove, system, uname, walk
from os import chmod, environ, listdir, remove, uname, walk
from os.path import expanduser
from pathlib import Path
from random import randint, SystemRandom
from random import randint
from shutil import get_terminal_size, move, rmtree
import sshync
from subprocess import CalledProcessError, Popen, PIPE, run
from sshync import delete as offline_delete, run_profile, make_profile, get_profile
from subprocess import CalledProcessError, DEVNULL, PIPE, run
from sys import argv, exit as s_exit
# BELOW - utility functions
# UTILITY FUNCTIONS
def entry_list_gen(_directory=path.expanduser('~/.local/share/sshyp/')): # generates and prints full entry list
def entry_list_gen(_directory=expanduser('~/.local/share/sshyp/')): # generates and prints full entry list
from textwrap import fill
print('\n\u001b[38;5;0;48;5;15msshyp entries:\u001b[0m\n')
_entry_list, _color_alternator = [], 1
@@ -34,8 +32,8 @@ def entry_list_gen(_directory=path.expanduser('~/.local/share/sshyp/')): # gene
print(fill(' '.join(_entry_list), width=_width) + '\n')
except ValueError:
pass
for _root, _directories, _files in walk(_directory):
for _dir in sorted(_directories):
for _root, _dirs, _files in walk(_directory):
for _dir in sorted(_dirs):
_inner_dir = f"{_root.replace(_directory, '')}/{_dir}"
print(f"\u001b[38;5;15;48;5;238m{_inner_dir}/\u001b[0m")
_entry_list, _color_alternator = [], 1
@@ -95,10 +93,11 @@ def entry_name_fetch(_entry_name_location): # fetches and returns entry name fr
if _entry_name.startswith('/'):
return _entry_name.replace('/', '', 1)
else:
return _entry_name
return _entry_name.strip()
def string_gen(_complexity, _length): # generates and returns a random string based on input
from random import SystemRandom
import string
if _complexity == 's':
_character_pool = string.ascii_letters + string.digits
@@ -119,12 +118,17 @@ def string_gen(_complexity, _length): # generates and returns a random string b
def pass_gen(): # prompts the user for necessary information to generate a password and passes it to string_gen
try:
_length = int(input('password length: '))
except ValueError:
print(f"\n\u001b[38;5;9merror: a non-integer value was input for password length\u001b[0m\n")
_gen = pass_gen()
return _gen
_length = 9
while True:
try:
_length = int(input('password length: '))
except ValueError:
continue
else:
if _length < 1:
continue
else:
break
_complexity = str(input('password complexity - simple (for compatibility) or complex (for security)? (s/C) '))
if _complexity != 's' and _complexity != 'S':
_complexity = 'c'
@@ -132,39 +136,38 @@ def pass_gen(): # prompts the user for necessary information to generate a pass
return _gen
def shm_gen(_tmp_dir=path.expanduser('~/.config/sshyp/tmp/')): # creates a temporary directory for entry editing
def shm_gen(_tmp_dir=expanduser('~/.config/sshyp/tmp/')): # creates a temporary directory for entry editing
_shm_folder_gen = string_gen('f', randint(12, 48))
_shm_entry_gen = string_gen('f', randint(12, 48))
Path(_tmp_dir + _shm_folder_gen).mkdir(0o700)
Path(_tmp_dir + _shm_folder_gen).mkdir(mode=0o700)
return _shm_folder_gen, _shm_entry_gen
def encrypt(_entry_dir, _shm_folder, _shm_entry, _gpg_com, _gpg_id, _tmp_dir=path.expanduser('~/.config/sshyp/tmp/')):
def encrypt(_entry_dir, _shm_folder, _shm_entry, _gpg_id, _tmp_dir=expanduser('~/.config/sshyp/tmp/')):
# encrypts an entry and cleans up the temporary files
system(f"{_gpg_com} -qr {str(_gpg_id)} -e '{_tmp_dir}{_shm_folder}/{_shm_entry}'")
run(['gpg', '-qr', str(_gpg_id), '-e', f"{_tmp_dir}{_shm_folder}/{_shm_entry}"])
move(f"{_tmp_dir}{_shm_folder}/{_shm_entry}.gpg", f"{_entry_dir}.gpg")
rmtree(f"{_tmp_dir}{_shm_folder}")
def decrypt(_entry_dir, _shm_folder, _shm_entry, _gpg_com, _quick_pass,
_tmp_dir=path.expanduser('~/.config/sshyp/tmp/')): # decrypts an entry to a temporary directory
def decrypt(_entry_dir, _shm_folder, _shm_entry, _quick_pass,
_tmp_dir=expanduser('~/.config/sshyp/tmp/')): # decrypts an entry to a temporary directory
if not isinstance(_quick_pass, bool):
_unlock_method = f"gpg --pinentry-mode loopback --passphrase '{_quick_pass}' -qd --output "
_unlock_method = ['gpg', '--pinentry-mode', 'loopback', '--passphrase', _quick_pass, '-qd', '--output']
else:
_unlock_method = f"{_gpg_com} -qd --output "
_unlock_method = ['gpg', '-qd', '--output']
if _shm_folder == 0 and _shm_entry == 0:
_output_target = f"/dev/null {path.expanduser('~/.config/sshyp/lock.gpg')}"
_output_target = ['/dev/null', expanduser('~/.config/sshyp/lock.gpg')]
else:
_output_target = f"'{_tmp_dir}{_shm_folder}/{_shm_entry}' '{_entry_dir}.gpg'"
_output_target = [f"{_tmp_dir}{_shm_folder}/{_shm_entry}", f"{_entry_dir}.gpg"]
try:
run(_unlock_method + _output_target, shell=True, stderr=PIPE, check=True, close_fds=True)
run(_unlock_method + _output_target, stderr=DEVNULL, check=True)
except CalledProcessError:
if not isinstance(_quick_pass, bool):
print('\n\u001b[38;5;9merror: quick-unlock failed as a result of an incorrect passphrase, an unreachable '
'sshyp server, or an invalid configuration\n\nfalling back to standard unlock\u001b[0m\n')
try:
run(f"{_gpg_com} -qd --output {_output_target}", shell=True, stderr=PIPE, check=True,
close_fds=True)
run(['gpg', '-qd', '--output'] + _output_target, stderr=DEVNULL, check=True)
except CalledProcessError:
print('\n\u001b[38;5;9merror: could not decrypt - ensure the correct gpg key is present\u001b[0m\n')
s_exit(5)
@@ -173,12 +176,11 @@ def decrypt(_entry_dir, _shm_folder, _shm_entry, _gpg_com, _quick_pass,
s_exit(5)
def determine_decrypt(_entry_dir, _shm_folder, _shm_entry, _gpg_com):
def determine_decrypt(_entry_dir, _shm_folder, _shm_entry):
if quick_unlock_enabled == 'y':
decrypt(_entry_dir, _shm_folder, _shm_entry, _gpg_com, whitelist_verify(port, username_ssh, ip,
client_device_id))
decrypt(_entry_dir, _shm_folder, _shm_entry, whitelist_verify(port, username_ssh, ip, client_device_id))
else:
decrypt(_entry_dir, _shm_folder, _shm_entry, _gpg_com, False)
decrypt(_entry_dir, _shm_folder, _shm_entry, False)
def optimized_edit(_lines, _edit_data, _edit_line): # ensures an edited entry is optimized for best compatibility
@@ -203,7 +205,7 @@ def optimized_edit(_lines, _edit_data, _edit_line): # ensures an edited entry i
def edit_note(_shm_folder, _shm_entry, _lines): # edits the note attached to an entry
_reg_lines = _lines[0:3]
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'w').writelines(_lines[3:])
system(f"{editor} '{tmp_dir}{_shm_folder}/{_shm_entry}-n'")
run([editor, f"{tmp_dir}{_shm_folder}/{_shm_entry}-n"])
_new_notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n").readlines()
while len(_reg_lines) < 3:
_reg_lines += ['\n']
@@ -213,76 +215,72 @@ def edit_note(_shm_folder, _shm_entry, _lines): # edits the note attached to an
def copy_id_check(_port, _username_ssh, _ip, _client_device_id):
# attempts to connect to the user's server via ssh to register the device for syncing
_command = f"ssh -o ConnectTimeout=3 -i '{path.expanduser('~/.ssh/sshyp')}' -p {_port} {_username_ssh}@{_ip} " \
f"\"touch '/home/{_username_ssh}/.config/sshyp/devices/{_client_device_id}'\""
try:
run(_command, shell=True, stderr=PIPE, check=True, close_fds=True)
run(['ssh', '-o', 'ConnectTimeout=3', '-i', expanduser('~/.ssh/sshyp'), '-p', _port, f"{_username_ssh}@{_ip}",
f'python3 -c \'from pathlib import Path; Path("/home/{_username_ssh}/.config/sshyp/devices/'
f'{_client_device_id}").touch(mode=0o400, exist_ok=True)\''], stderr=DEVNULL, check=True)
except CalledProcessError:
print('\n\u001b[38;5;9mwarning: ssh connection could not be made - ensure the public key (~/.ssh/sshyp.pub) is '
'registered on the remote server and that the entered ip, port, and username are correct\n\nsyncing '
'functionality will be disabled until this is addressed\u001b[0m\n')
open(path.expanduser('~/.config/sshyp/ssh-error'), 'w').write('1')
open(expanduser('~/.config/sshyp/ssh-error'), 'w').write('1')
return 1
open(path.expanduser('~/.config/sshyp/ssh-error'), 'w').write('0')
open(expanduser('~/.config/sshyp/ssh-error'), 'w').write('0')
return 0
# BELOW - argument-specific functions
# ARGUMENT-SPECIFIC FUNCTIONS
def tweak(): # runs configuration wizard
from os import symlink
_divider = f"\n{'=' * (get_terminal_size()[0] - int((.5 * get_terminal_size()[0])))}\n\n"
# config directory creation
Path(path.expanduser('~/.config/sshyp/devices')).mkdir(0o700, parents=True, exist_ok=True)
if not Path(f"{path.expanduser('~/.config/sshyp/tmp')}").exists():
Path(expanduser('~/.config/sshyp/devices')).mkdir(mode=0o700, parents=True, exist_ok=True)
if not Path(f"{expanduser('~/.config/sshyp/tmp')}").exists():
if uname()[0] == 'Haiku' or uname()[0] == 'FreeBSD':
system(f"ln -s /tmp {path.expanduser('~/.config/sshyp/tmp')}")
symlink('/tmp', 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')}")
symlink('/data/data/com.termux/files/usr/tmp', expanduser('~/.config/sshyp/tmp'))
else:
system(f"ln -s /dev/shm {path.expanduser('~/.config/sshyp/tmp')}")
symlink('/dev/shm', expanduser('~/.config/sshyp/tmp'))
# device type configuration
_device_type = input('\nclient or server installation? (C/s) ')
if _device_type.lower() == 's':
_sshyp_data = ['server']
Path(path.expanduser('~/.config/sshyp/deleted')).mkdir(0o700, exist_ok=True)
Path(path.expanduser('~/.config/sshyp/whitelist')).mkdir(0o700, exist_ok=True)
Path(expanduser('~/.config/sshyp/deleted')).mkdir(mode=0o700, exist_ok=True)
Path(expanduser('~/.config/sshyp/whitelist')).mkdir(mode=0o700, exist_ok=True)
print(f"\n\u001b[4;1mmake sure the ssh service is running and properly configured\u001b[0m")
else:
_sshyp_data = ['client']
Path(path.expanduser('~/.local/share/sshyp')).mkdir(0o700, parents=True, exist_ok=True)
Path(expanduser('~/.local/share/sshyp')).mkdir(mode=0o700, parents=True, exist_ok=True)
# gpg configuration
_gpg_gen = input(f"{_divider}sshyp requires the use of a unique gpg key - use an (e)xisting key or (g)enerate a"
f" new one? (E/g) ")
if _gpg_gen.lower() != 'g':
system(f"{gpg} -k")
run(['gpg', '-k'])
_sshyp_data += [str(input('gpg key id: '))]
else:
print('\na unique gpg key is being generated for you...')
if not Path(path.expanduser('~/.config/sshyp/gpg-gen')).is_file():
open(path.expanduser('~/.config/sshyp/gpg-gen'), 'w').writelines([
if not Path(expanduser('~/.config/sshyp/gpg-gen')).is_file():
open(expanduser('~/.config/sshyp/gpg-gen'), 'w').writelines([
'Key-Type: 1\n', 'Key-Length: 4096\n', 'Key-Usage: sign encrypt\n', 'Name-Real: sshyp\n',
'Name-Comment: gpg-sshyp\n', 'Name-Email: https://github.com/rwinkhart/sshyp\n', 'Expire-Date: 0'])
if uname()[0] == 'Haiku':
run(gpg + ' --batch --generate-key --passphrase ' + "'" +
input('\ngpg passphrase: ') + "'" + " '" +
path.expanduser('~/.config/sshyp/gpg-gen') + "'", shell=True)
else:
run(f"{gpg} --batch --generate-key '{path.expanduser('~/.config/sshyp/gpg-gen')}'", shell=True)
remove(path.expanduser('~/.config/sshyp/gpg-gen'))
_sshyp_data += [run(f"{gpg} -k", shell=True, stdout=PIPE, text=True).stdout.split('\n')[-4].strip()]
run(['gpg', '--batch', '--generate-key', expanduser('~/.config/sshyp/gpg-gen')])
remove(expanduser('~/.config/sshyp/gpg-gen'))
_sshyp_data += [run(['gpg', '-k'], stdout=PIPE, text=True).stdout.split('\n')[-4].strip()]
# text editor configuration
_sshyp_data += [input(f"{_divider}example input: vim\n\npreferred text editor: ")]
# lock file generation
if Path(path.expanduser('~/.config/sshyp/lock.gpg')).is_file():
remove(path.expanduser('~/.config/sshyp/lock.gpg'))
open(path.expanduser('~/.config/sshyp/lock'), 'w')
system(f"{gpg} -qr {str(_sshyp_data[1])} -e {path.expanduser('~/.config/sshyp/lock')}")
remove(path.expanduser('~/.config/sshyp/lock'))
if Path(expanduser('~/.config/sshyp/lock.gpg')).is_file():
remove(expanduser('~/.config/sshyp/lock.gpg'))
open(expanduser('~/.config/sshyp/lock'), 'w')
run(['gpg', '-qr', str(_sshyp_data[1]), '-e', expanduser('~/.config/sshyp/lock')])
remove(expanduser('~/.config/sshyp/lock'))
# ssh key configuration
_offline_mode = False
@@ -290,12 +288,11 @@ def tweak(): # runs configuration wizard
f"configured\n\nsync support requires a unique ssh key - would you like to have this "
f"automatically generated? (Y/n/o(ffline)) "))
if _ssh_gen.lower() != 'n' and _ssh_gen.lower() != 'o' and _ssh_gen.lower() != 'offline':
if uname()[0] == 'Haiku':
Path(f"{path.expanduser('~')}/.ssh").mkdir(0o700, exist_ok=True)
system('ssh-keygen -t ed25519 -f ~/.ssh/sshyp')
Path(f"{expanduser('~')}/.ssh").mkdir(mode=0o700, exist_ok=True)
run(['ssh-keygen', '-t', 'ed25519', '-f', expanduser('~/.ssh/sshyp')])
elif _ssh_gen.lower() == 'n':
print(f"\n\u001b[4;1mensure that the key file you are using is located at "
f"{path.expanduser('~/.ssh/sshyp')}\u001b[0m")
f"{expanduser('~/.ssh/sshyp')}\u001b[0m")
elif _ssh_gen.lower() == 'o' or _ssh_gen.lower() == 'offline':
_offline_mode = True
print('\nsshyp has been set to offline mode - to enable syncing, run "sshyp tweak" again')
@@ -309,19 +306,19 @@ def tweak(): # runs configuration wizard
_username_ssh = str(input('\nusername of the remote server: '))
# sshync profile generation
sshync.make_profile(path.expanduser('~/.config/sshyp/sshyp.sshync'),
path.expanduser('~/.local/share/sshyp/'), f"/home/{_username_ssh}/.local/share/sshyp/",
path.expanduser('~/.ssh/sshyp'), _ip, _port, _username_ssh)
make_profile(expanduser('~/.config/sshyp/sshyp.sshync'),
expanduser('~/.local/share/sshyp/'), f"/home/{_username_ssh}/.local/share/sshyp/",
expanduser('~/.ssh/sshyp'), _ip, _port, _username_ssh)
# device id configuration
for _id in listdir(path.expanduser('~/.config/sshyp/devices')): # remove existing device id
remove(f"{path.expanduser('~/.config/sshyp/devices/')}{_id}")
for _id in listdir(expanduser('~/.config/sshyp/devices')): # remove existing device id
remove(f"{expanduser('~/.config/sshyp/devices/')}{_id}")
print(f"{_divider}\u001b[4;1mimportant:\u001b[0m this id \u001b[4;1mmust\u001b[0m be unique amongst your "
f"client devices\n\nthis is used to keep track of database syncing and quick-unlock permissions\n")
_device_id_prefix = str(input('device id: ')) + '-'
_device_id_suffix = string_gen('f', randint(24, 48))
_device_id = _device_id_prefix + _device_id_suffix
open(f"{path.expanduser('~/.config/sshyp/devices/')}{_device_id}", 'w')
open(f"{expanduser('~/.config/sshyp/devices/')}{_device_id}", 'w')
# quick-unlock configuration
print(f"{_divider}this allows you to use a shorter version of your gpg key password and\n"
@@ -335,11 +332,11 @@ def tweak(): # runs configuration wizard
# test server connection and attempt to register device id
copy_id_check(_port, _username_ssh, _ip, _device_id)
elif Path(path.expanduser('~/.config/sshyp/sshyp.sshync')).is_file():
remove(path.expanduser('~/.config/sshyp/sshyp.sshync'))
elif Path(expanduser('~/.config/sshyp/sshyp.sshync')).is_file():
remove(expanduser('~/.config/sshyp/sshyp.sshync'))
# write main config file (sshyp-data)
with open(path.expanduser('~/.config/sshyp/sshyp-data'), 'w') as _config_file:
with open(expanduser('~/.config/sshyp/sshyp-data'), 'w') as _config_file:
_lines = 0
for _item in _sshyp_data:
_lines += 1
@@ -402,9 +399,9 @@ def print_info(): # prints help text based on argument
elif argument_list[1] == 'version' or argument_list[1] == '-v':
print('\nsshyp is a simple, self-hosted, sftp-synchronized password manager\nfor unix(-like) systems (haiku/'
'freebsd/linux/termux)\n\nsshyp is a viable alternative to (and compatible with) pass/password-store\n')
print(" .. \u001b[38;5;9m♥♥ ♥♥\u001b[0m ..\n .''.''/()\\ \u001b[38;5;13m"
print(" .. \u001b[38;5;9m♥♥ ♥♥\u001b[0m ..\n .''.''/()\\ \u001b[38;5;10m"
"♥♥♥♥♥♥♥\u001b[0m /()\\''.''.\n * : \u001b[38;5;9m♥♥♥♥♥\u001b[0m : *"
"\n `..'..' \u001b[38;5;13m♥♥♥\u001b[0m `..'..'\n // \\\\ "
"\n `..'..' \u001b[38;5;10m♥♥♥\u001b[0m `..'..'\n // \\\\ "
"\u001b[38;5;9m♥\u001b[0m // \\\\")
print('\u001b[38;5;7;48;5;8m<><><><><><><><><><><><><><><><><><><><><><><><><><><><>\u001b[0m')
print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m '
@@ -413,10 +410,10 @@ def print_info(): # prints help text based on argument
'randall winkhart\u001b[38;5;15;48;5;15m \u001b[38;5;7;48;5;8m/\u001b[0m')
print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m '
'\u001b[38;5;7;48;5;8m/\u001b[0m')
print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m \u001b[38;5;15;48;5;8mversion 1.2.0'
print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m \u001b[38;5;15;48;5;8mversion 1.3.0'
'\u001b[38;5;15;48;5;15m \u001b[38;5;7;48;5;8m/\u001b[0m')
print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m \u001b[38;5;15;48;5;8mthe brisk bahh '
'update\u001b[38;5;15;48;5;15m \u001b[38;5;7;48;5;8m/\u001b[0m')
print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m \u001b[38;5;15;48;5;8mthe serious shepherd '
'update\u001b[38;5;15;48;5;15m \u001b[38;5;7;48;5;8m/\u001b[0m')
print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m '
'\u001b[38;5;7;48;5;8m/\u001b[0m')
print('\u001b[38;5;7;48;5;8m<><><><><><><><><><><><><><><><><><><><><><><><><><><><>\u001b[0m\n')
@@ -472,7 +469,7 @@ def no_arg(): # displays a list of entries and gives an option to select one fo
print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) does not exist\u001b[0m\n")
s_exit(3)
_shm_folder, _shm_entry = shm_gen()
determine_decrypt(directory + _entry_name, _shm_folder, _shm_entry, gpg)
determine_decrypt(directory + _entry_name, _shm_folder, _shm_entry)
entry_reader(f"{tmp_dir}{_shm_folder}/{_shm_entry}")
rmtree(f"{tmp_dir}{_shm_folder}")
@@ -482,57 +479,20 @@ def read_shortcut(): # shortcut to quickly read an entry
print(f"\n\u001b[38;5;9merror: entry ({argument.replace('/', '', 1)}) does not exist\u001b[0m\n")
s_exit(3)
_shm_folder, _shm_entry = shm_gen()
determine_decrypt(directory + argument.replace('/', '', 1), _shm_folder, _shm_entry, gpg)
determine_decrypt(directory + argument.replace('/', '', 1), _shm_folder, _shm_entry)
entry_reader(f"{tmp_dir}{_shm_folder}/{_shm_entry}")
rmtree(f"{tmp_dir}{_shm_folder}")
def sync(): # calls sshync to sync changes to the user's server
print('\nsyncing entries with the server device...\n')
# check for deletions
system(f"ssh -i '{path.expanduser('~/.ssh/sshyp')}' -p {port} {username_ssh}@{ip} \"cd /lib/sshyp; python -c "
f"'import sshypRemote; sshypRemote.deletion_check(\"'\"{client_device_id}\"'\")'\"")
system(f"scp -pqs -P {port} -i '{path.expanduser('~/.ssh/sshyp')}' {username_ssh}@{ip}:'/home/{username_ssh}"
f"/.config/sshyp/deletion_database' {path.expanduser('~/.config/sshyp/')}")
try:
_deletion_database = open(path.expanduser('~/.config/sshyp/deletion_database')).readlines()
except (FileNotFoundError, IndexError):
print('\n\u001b[38;5;9merror: the deletion database does not exist or is corrupted\u001b[0m\n')
_deletion_database = None
s_exit(6)
for _file in _deletion_database:
try:
if silent_sync != 1:
print(f"\u001b[38;5;208m{_file[:-1]}\u001b[0m has been sheared, removing...")
if _file[:-1].endswith('/'):
rmtree(f"{directory}{_file[:-1]}")
else:
remove(f"{directory}{_file[:-1]}.gpg")
except FileNotFoundError:
if silent_sync != 1:
print('location does not exist locally')
# check for new folders
system(f"ssh -i '{path.expanduser('~/.ssh/sshyp')}' -p {port} {username_ssh}@{ip} \"cd /lib/sshyp; python -c "
f"'import sshypRemote; sshypRemote.folder_check()'\"")
system(f"scp -pqs -P {port} -i '{path.expanduser('~/.ssh/sshyp')}' {username_ssh}@{ip}:'/home/{username_ssh}"
f"/.config/sshyp/folder_database' {path.expanduser('~/.config/sshyp/')}")
try:
_folder_database = open(path.expanduser('~/.config/sshyp/folder_database')).readlines()
except (FileNotFoundError, IndexError):
print('\n\u001b[38;5;9merror: the folder database does not exist or is corrupted\u001b[0m\n')
_folder_database = None
s_exit(6)
for _folder in _folder_database:
if Path(f"{path.expanduser('~')}{_folder[:-1]}").is_dir():
pass
else:
print(f"\u001b[38;5;2m{_folder.replace('/.local/share/sshyp/', '')[:-1]}/\u001b[0m does not exist locally, "
f"creating...")
Path(f"{path.expanduser('~')}{_folder[:-1]}").mkdir(0o700, parents=True, exist_ok=True)
# set permissions before uploading
system('find ' + directory + ' -type d -exec chmod -R 700 {} +')
system('find ' + directory + ' -type f -exec chmod -R 600 {} +')
sshync.run_profile(path.expanduser('~/.config/sshyp/sshyp.sshync'))
for _root, _dirs, _files in walk(expanduser('~/.local/share/sshyp')):
for _path in _root.split('\n'):
chmod(_path, 0o700)
for _file in _files:
chmod(_root + '/' + _file, 0o600)
run_profile(expanduser('~/.config/sshyp/sshyp.sshync'), silent_sync)
def whitelist_setup(): # takes input from the user to set up quick-unlock password
@@ -554,39 +514,40 @@ def whitelist_setup(): # takes input from the user to set up quick-unlock passw
_i += 1
# create assembly key
open(path.expanduser('~/.config/sshyp/gpg-gen'), 'w').writelines([
open(expanduser('~/.config/sshyp/gpg-gen'), 'w').writelines([
'Key-Type: 1\n', 'Key-Length: 4096\n', 'Key-Usage: sign encrypt\n', 'Name-Real: sshyp\n',
'Name-Comment: gpg-sshyp-whitelist\n', 'Name-Email: https://github.com/rwinkhart/sshyp\n', 'Expire-Date: 0'])
run('gpg -q --pinentry-mode loopback --batch --generate-key --passphrase ' + "'" + _quick_unlock_password + "'" +
" '" + path.expanduser('~/.config/sshyp/gpg-gen') + "'", shell=True)
remove(path.expanduser('~/.config/sshyp/gpg-gen'))
_gpg_id = run(f"{gpg} -k", shell=True, stdout=PIPE, text=True).stdout.split('\n')[-4].strip()
run(['gpg', '-q', '--pinentry-mode', 'loopback', '--batch', '--generate-key', '--passphrase',
_quick_unlock_password, expanduser('~/.config/sshyp/gpg-gen')])
remove(expanduser('~/.config/sshyp/gpg-gen'))
_gpg_id = run(['gpg', '-k'], stdout=PIPE, text=True).stdout.split('\n')[-4].strip()
# encrypt excluded with the assembly key
_shm_folder, _shm_entry = shm_gen()
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w').write(_quick_unlock_password_excluded)
encrypt(path.expanduser('~/.config/sshyp/excluded'), _shm_folder, _shm_entry, gpg, _gpg_id)
encrypt(expanduser('~/.config/sshyp/excluded'), _shm_folder, _shm_entry, _gpg_id)
print(f"\nyour quick-unlock passphrase: {_quick_unlock_password}")
def whitelist_verify(_port, _username_ssh, _ip, _client_device_id):
# checks the user's whitelist status and fetches the full gpg key password if possible
try:
run(f"gpg --pinentry-mode cancel -qd --output /dev/null {path.expanduser('~/.config/sshyp/lock.gpg')}",
shell=True, stderr=PIPE, check=True, close_fds=True)
run(['gpg', '--pinentry-mode', 'cancel', '-qd', '--output', '/dev/null',
expanduser('~/.config/sshyp/lock.gpg')], stderr=DEVNULL, check=True)
return False
except CalledProcessError:
_i, _full_password = 0, ''
_server_whitelist = run('ssh -i ' + "'" + path.expanduser('~/.ssh/sshyp') + "' -p " + _port + " " +
_username_ssh + '@' + _ip + " 'ls ~/.config/sshyp/whitelist'",
shell=True, stdout=PIPE, text=True)
for _device_id in _server_whitelist.stdout.rstrip().split('\n'):
_server_whitelist = run(['ssh', '-i', expanduser('~/.ssh/sshyp'), '-p', _port, f"{_username_ssh}@{_ip}",
f'python3 -c \'from os import listdir; print(*listdir("/home/{_username_ssh}'
f'/.config/sshyp/whitelist"))\''], stdout=PIPE, text=True).stdout.rstrip().split(' ')
for _device_id in _server_whitelist:
if _device_id == _client_device_id:
_quick_unlock_password = input('\nquick-unlock passphrase: ')
from getpass import getpass
_quick_unlock_password = getpass(prompt='\nquick-unlock passphrase: ')
_quick_unlock_password_excluded = \
run('ssh -i ' + "'" + path.expanduser('~/.ssh/sshyp') + "' -p " + _port + " " + _username_ssh + '@'
+ _ip + f" 'gpg --pinentry-mode loopback --passphrase '{_quick_unlock_password}' -qd"
f" ~/.config/sshyp/excluded.gpg'", shell=True, stdout=PIPE, text=True).stdout.rstrip()
run(['ssh', '-i', expanduser('~/.ssh/sshyp'), '-p', _port, f"{_username_ssh}@{_ip}",
f"gpg --pinentry-mode loopback --passphrase '{_quick_unlock_password}' "
f"-qd ~/.config/sshyp/excluded.gpg"], stdout=PIPE, text=True).stdout.rstrip()
while _i < len(_quick_unlock_password_excluded):
try:
_full_password += _quick_unlock_password_excluded[_i]
@@ -602,8 +563,8 @@ def whitelist_verify(_port, _username_ssh, _ip, _client_device_id):
def whitelist_list(): # shows the quick-unlock whitelist status of device ids
_whitelisted_ids = listdir(path.expanduser('~/.config/sshyp/whitelist'))
_device_ids = listdir(path.expanduser('~/.config/sshyp/devices'))
_whitelisted_ids = listdir(expanduser('~/.config/sshyp/whitelist'))
_device_ids = listdir(expanduser('~/.config/sshyp/devices'))
print('\n\u001b[1mquick-unlock whitelisted device ids:\u001b[0m')
for _id in _whitelisted_ids:
print(_id)
@@ -624,15 +585,15 @@ def whitelist_manage(): # adds or removes quick-unlock whitelisted device ids
_device_id = ' '.join(_argument_split)
if argument_list[2] == 'add':
if _device_id in listdir(path.expanduser('~/.config/sshyp/devices')):
open(path.expanduser(f"~/.config/sshyp/whitelist/{_device_id}"), 'w').write('')
if _device_id in listdir(expanduser('~/.config/sshyp/devices')):
open(expanduser(f"~/.config/sshyp/whitelist/{_device_id}"), 'w').write('')
whitelist_list()
else:
print(f"\n\u001b[38;5;9merror: device id ({_device_id}) is not registered\u001b[0m\n")
s_exit(2)
elif Path(path.expanduser(f"~/.config/sshyp/whitelist/{_device_id}")).is_file():
remove(path.expanduser(f"~/.config/sshyp/whitelist/{_device_id}"))
elif Path(expanduser(f"~/.config/sshyp/whitelist/{_device_id}")).is_file():
remove(expanduser(f"~/.config/sshyp/whitelist/{_device_id}"))
whitelist_list()
@@ -647,7 +608,7 @@ def add_entry(): # adds a new entry
s_exit(4)
if argument_list[2] == 'note' or argument_list[2] == '-n':
_shm_folder, _shm_entry = shm_gen()
system(f"{editor} '{tmp_dir}{_shm_folder}/{_shm_entry}-n'")
run([editor, f"{tmp_dir}{_shm_folder}/{_shm_entry}-n"])
_notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'r').read()
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w').writelines(optimized_edit(['', '', '', _notes], None, -1))
elif argument_list[2] == 'password' or argument_list[2] == '-p':
@@ -657,7 +618,7 @@ def add_entry(): # adds a new entry
_add_note = input('add a note to this entry? (y/N) ')
_shm_folder, _shm_entry = shm_gen()
if _add_note.lower() == 'y':
system(f"{editor} '{tmp_dir}{_shm_folder}/{_shm_entry}-n'")
run([editor, f"{tmp_dir}{_shm_folder}/{_shm_entry}-n"])
_notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'r').read()
else:
_notes = ''
@@ -665,7 +626,7 @@ def add_entry(): # adds a new entry
.writelines(optimized_edit([_password, _username, _url, _notes], None, -1))
print('\n\u001b[1mentry preview:\u001b[0m')
entry_reader(f"{tmp_dir}{_shm_folder}/{_shm_entry}")
encrypt(directory + _entry_name, _shm_folder, _shm_entry, gpg, gpg_id)
encrypt(directory + _entry_name, _shm_folder, _shm_entry, gpg_id)
def add_folder(): # creates a new folder
@@ -673,13 +634,15 @@ def add_folder(): # creates a new folder
_entry_name = entry_name_fetch('name of new folder: ')
else:
_entry_name = entry_name_fetch(2)
Path(directory + _entry_name).mkdir(0o700)
Path(directory + _entry_name).mkdir(mode=0o700, parents=True, exist_ok=True)
if ssh_error != 1:
system(f"ssh -i '{path.expanduser('~/.ssh/sshyp')}' -p {port} {username_ssh}@{ip} \"mkdir -p "
f"'{directory_ssh}{_entry_name}'\"")
run(['ssh', '-i', expanduser('~/.ssh/sshyp'), '-p', port, f"{username_ssh}@{ip}",
f'python3 -c \'from pathlib import Path; Path("{directory_ssh}{_entry_name}")'
f'.mkdir(mode=0o700, parents=True, exist_ok=True)\''])
def rename(): # renames an entry or folder
from shutil import copy
if argument == 'edit rename' or argument == 'edit relocate' or argument == 'edit -r':
_entry_name = entry_name_fetch('entry/folder to rename/relocate: ')
else:
@@ -692,15 +655,21 @@ def rename(): # renames an entry or folder
print(f"\n\u001b[38;5;9merror: ({_new_name}) already exists\u001b[0m\n")
s_exit(4)
if _entry_name.endswith('/'):
move(f"{directory}{_entry_name}", f"{directory}{_new_name}")
if ssh_error != 1:
system(f"ssh -i '{path.expanduser('~/.ssh/sshyp')}' -p {port} {username_ssh}@{ip} \"mkdir -p "
f"'{directory_ssh}{_new_name}'\"")
Path(f"{directory}{_new_name}").mkdir(mode=0o700, parents=True, exist_ok=True)
run(['ssh', '-i', expanduser('~/.ssh/sshyp'), '-p', port, f"{username_ssh}@{ip}",
f'python3 -c \'from pathlib import Path; Path("{directory_ssh}{_new_name}")'
f'.mkdir(mode=0o700, parents=True, exist_ok=True)\''])
else:
move(f"{directory}{_entry_name}", f"{directory}{_new_name}")
else:
move(f"{directory}{_entry_name}.gpg", f"{directory}{_new_name}.gpg")
if ssh_error != 1:
copy(f"{directory}{_entry_name}.gpg", f"{directory}{_new_name}.gpg")
else:
move(f"{directory}{_entry_name}.gpg", f"{directory}{_new_name}.gpg")
if ssh_error != 1:
system(f"ssh -i '{path.expanduser('~/.ssh/sshyp')}' -p {port} {username_ssh}@{ip} \"cd /lib/sshyp; python -c "
f"'import sshypRemote; sshypRemote.delete(\"'\"{_entry_name}\"'\", \"'\"remotely\"'\")'\"")
run(['ssh', '-i', expanduser('~/.ssh/sshyp'), '-p', port, f"{username_ssh}@{ip}",
f'cd /lib/sshyp; python3 -c \'from sshync import delete; delete("{_entry_name}", "remotely")\''])
def edit(): # edits the contents of an entry
@@ -713,7 +682,7 @@ def edit(): # edits the contents of an entry
print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) does not exist\u001b[0m\n")
s_exit(3)
_shm_folder, _shm_entry = shm_gen()
determine_decrypt(directory + _entry_name, _shm_folder, _shm_entry, gpg)
determine_decrypt(directory + _entry_name, _shm_folder, _shm_entry)
if argument_list[2] == 'username' or argument_list[2] == '-u':
_detail, _edit_line = str(input('username: ')), 1
elif argument_list[2] == 'password' or argument_list[2] == '-p':
@@ -730,7 +699,7 @@ def edit(): # edits the contents of an entry
remove(f"{directory}{_entry_name}.gpg")
print('\n\u001b[1mentry preview:\u001b[0m')
entry_reader(f"{tmp_dir}{_shm_folder}/{_shm_entry}")
encrypt(directory + _entry_name, _shm_folder, _shm_entry, gpg, gpg_id)
encrypt(directory + _entry_name, _shm_folder, _shm_entry, gpg_id)
def gen(): # generates a password for a new or an existing entry
@@ -754,7 +723,7 @@ def gen(): # generates a password for a new or an existing entry
_add_note = input('add a note to this entry? (y/N) ')
_shm_folder, _shm_entry = shm_gen()
if _add_note.lower() == 'y':
system(f"{editor} '{tmp_dir}{_shm_folder}/{_shm_entry}-n'")
run([editor, f"{tmp_dir}{_shm_folder}/{_shm_entry}-n"])
_notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'r').read()
else:
_notes = ''
@@ -762,16 +731,17 @@ def gen(): # generates a password for a new or an existing entry
.writelines(optimized_edit([_password, _username, _url, _notes], None, -1))
else:
_shm_folder, _shm_entry = shm_gen()
determine_decrypt(directory + _entry_name, _shm_folder, _shm_entry, gpg)
determine_decrypt(directory + _entry_name, _shm_folder, _shm_entry)
_new_lines = optimized_edit(open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'r').readlines(), pass_gen(), 0)
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w').writelines(_new_lines)
remove(f"{directory}{_entry_name}.gpg")
print('\n\u001b[1mentry preview:\u001b[0m')
entry_reader(f"{tmp_dir}{_shm_folder}/{_shm_entry}")
encrypt(directory + _entry_name, _shm_folder, _shm_entry, gpg, gpg_id)
encrypt(directory + _entry_name, _shm_folder, _shm_entry, gpg_id)
def copy_data(): # copies a specified field of an entry to the clipboard
from subprocess import Popen
if len(argument_list) < 4:
_entry_name = entry_name_fetch('entry to copy: ')
else:
@@ -780,48 +750,28 @@ def copy_data(): # copies a specified field of an entry to the clipboard
print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) does not exist\u001b[0m\n")
s_exit(3)
_shm_folder, _shm_entry = shm_gen()
determine_decrypt(directory + _entry_name, _shm_folder, _shm_entry, gpg)
_copy_line = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'r').readlines()
if uname()[0] == 'Haiku': # Haiku clipboard detection
if argument_list[2] == 'username' or argument_list[2] == '-u':
system('clipboard -c ' + "'" + _copy_line[1].rstrip().replace("'", "'\\''") + "'")
elif argument_list[2] == 'password' or argument_list[2] == '-p':
system('clipboard -c ' + "'" + _copy_line[0].rstrip().replace("'", "'\\''") + "'")
elif argument_list[2] == 'url' or argument_list[2] == '-l':
system('clipboard -c ' + "'" + _copy_line[2].rstrip().replace("'", "'\\''") + "'")
elif argument_list[2] == 'note' or argument_list[2] == '-n':
system('clipboard -c ' + "'" + _copy_line[3].rstrip().replace("'", "'\\''") + "'")
Popen('sleep 30; clipboard -r', shell=True, close_fds=True)
determine_decrypt(directory + _entry_name, _shm_folder, _shm_entry)
_copy_line, _index = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'r').readlines(), 0
if argument_list[2] == 'username' or argument_list[2] == '-u':
_index = 1
elif argument_list[2] == 'password' or argument_list[2] == '-p':
_index = 0
elif argument_list[2] == 'url' or argument_list[2] == '-l':
_index = 2
elif argument_list[2] == 'note' or argument_list[2] == '-n':
_index = 3
if 'WAYLAND_DISPLAY' in environ: # Wayland clipboard detection
run(['wl-copy', _copy_line[_index].rstrip()])
Popen('sleep 30; wl-copy -c', shell=True)
elif uname()[0] == 'Haiku': # Haiku clipboard detection
run(['clipboard', '-c', _copy_line[_index].rstrip()])
Popen('sleep 30; clipboard -r', shell=True)
elif Path("/data/data/com.termux").exists(): # Termux (Android) clipboard detection
if argument_list[2] == 'username' or argument_list[2] == '-u':
system('termux-clipboard-set ' + "'" + _copy_line[1].rstrip().replace("'", "'\\''") + "'")
elif argument_list[2] == 'password' or argument_list[2] == '-p':
system('termux-clipboard-set ' + "'" + _copy_line[0].rstrip().replace("'", "'\\''") + "'")
elif argument_list[2] == 'url' or argument_list[2] == '-l':
system('termux-clipboard-set ' + "'" + _copy_line[2].rstrip().replace("'", "'\\''") + "'")
elif argument_list[2] == 'note' or argument_list[2] == '-n':
system('termux-clipboard-set ' + "'" + _copy_line[3].rstrip().replace("'", "'\\''") + "'")
Popen("sleep 30; termux-clipboard-set ''", shell=True, close_fds=True)
elif environ.get('WAYLAND_DISPLAY') == 'wayland-0': # Wayland clipboard detection
if argument_list[2] == 'username' or argument_list[2] == '-u':
system('wl-copy ' + "'" + _copy_line[1].rstrip().replace("'", "'\\''") + "'")
elif argument_list[2] == 'password' or argument_list[2] == '-p':
system('wl-copy ' + "'" + _copy_line[0].rstrip().replace("'", "'\\''") + "'")
elif argument_list[2] == 'url' or argument_list[2] == '-l':
system('wl-copy ' + "'" + _copy_line[2].rstrip().replace("'", "'\\''") + "'")
elif argument_list[2] == 'note' or argument_list[2] == '-n':
system('wl-copy ' + "'" + _copy_line[3].rstrip().replace("'", "'\\''") + "'")
Popen('sleep 30; wl-copy -c', shell=True, close_fds=True)
run(['termux-clipboard-set', _copy_line[_index].rstrip()])
Popen("sleep 30; termux-clipboard-set ''", shell=True)
else: # X11 clipboard detection
if argument_list[2] == 'username' or argument_list[2] == '-u':
system('echo -n ' + "'" + _copy_line[1].rstrip().replace("'", "'\\''") + "'" + ' | xclip -sel c')
elif argument_list[2] == 'password' or argument_list[2] == '-p':
system('echo -n ' + "'" + _copy_line[0].rstrip().replace("'", "'\\''") + "'" + ' | xclip -sel c')
elif argument_list[2] == 'url' or argument_list[2] == '-l':
system('echo -n ' + "'" + _copy_line[2].rstrip().replace("'", "'\\''") + "'" + ' | xclip -sel c')
elif argument_list[2] == 'note' or argument_list[2] == '-n':
system('echo -n ' + "'" + _copy_line[3].rstrip().replace("'", "'\\''") + "'" + ' | xclip -sel c')
Popen("sleep 30; echo -n '' | xclip -sel c", shell=True, close_fds=True)
run(['xclip', '-sel', 'c'], stdin=Popen(['echo', '-n', _copy_line[_index].rstrip()], stdout=PIPE).stdout)
Popen("sleep 30; echo -n '' | xclip -sel c", shell=True)
rmtree(f"{tmp_dir}{_shm_folder}")
@@ -830,13 +780,12 @@ def remove_data(): # deletes an entry from the server and flags it for local de
_entry_name = entry_name_fetch('entry/folder to shear: ')
else:
_entry_name = entry_name_fetch(1)
determine_decrypt(path.expanduser('~/.config/sshyp/lock.gpg'), 0, 0, gpg)
determine_decrypt(expanduser('~/.config/sshyp/lock.gpg'), 0, 0)
if ssh_error != 1:
system(f"ssh -i '{path.expanduser('~/.ssh/sshyp')}' -p {port} {username_ssh}@{ip} \"cd /lib/sshyp; python -c "
f"'import sshypRemote; sshypRemote.delete(\"'\"{_entry_name}\"'\", \"'\"remotely\"'\")'\"")
run(['ssh', '-i', expanduser('~/.ssh/sshyp'), '-p', port, f"{username_ssh}@{ip}",
f'cd /lib/sshyp; python3 -c \'from sshync import delete; delete("{_entry_name}", "remotely")\''])
else:
from sshypRemote import delete as offline_delete
offline_delete(_entry_name, '')
offline_delete(_entry_name, 'locally')
if __name__ == "__main__":
@@ -845,31 +794,27 @@ if __name__ == "__main__":
# retrieve typed argument
argument_list = argv
argument = ' '.join(argument_list[1:])
if uname()[0] == 'Haiku': # set proper gpg command for OS
gpg = 'gpg --pinentry-mode loopback'
else:
gpg = 'gpg'
# import saved userdata
device_type = ''
if argument != 'tweak':
tmp_dir = path.expanduser('~/.config/sshyp/tmp/')
tmp_dir = expanduser('~/.config/sshyp/tmp/')
try:
sshyp_data = open(path.expanduser('~/.config/sshyp/sshyp-data')).readlines()
sshyp_data = open(expanduser('~/.config/sshyp/sshyp-data')).readlines()
device_type = sshyp_data[0].rstrip()
if device_type == 'client':
directory = path.expanduser('~/.local/share/sshyp/')
directory = expanduser('~/.local/share/sshyp/')
gpg_id = sshyp_data[1].rstrip()
editor = sshyp_data[2].rstrip()
quick_unlock_enabled = sshyp_data[3].rstrip()
if Path(path.expanduser('~/.config/sshyp/sshyp.sshync')).is_file():
ssh_info = sshync.get_profile(path.expanduser('~/.config/sshyp/sshyp.sshync'))
if Path(expanduser('~/.config/sshyp/sshyp.sshync')).is_file():
ssh_info = get_profile(expanduser('~/.config/sshyp/sshyp.sshync'))
username_ssh = ssh_info[0].rstrip()
ip = ssh_info[1].rstrip()
port = ssh_info[2].rstrip()
directory_ssh = str(ssh_info[4].rstrip())
client_device_id = listdir(path.expanduser('~/.config/sshyp/devices'))[0].rstrip()
ssh_error = int(open(path.expanduser('~/.config/sshyp/ssh-error')).read().rstrip())
client_device_id = listdir(expanduser('~/.config/sshyp/devices'))[0].rstrip()
ssh_error = int(open(expanduser('~/.config/sshyp/ssh-error')).read().rstrip())
if ssh_error != 0:
ssh_error = copy_id_check(port, username_ssh, ip, client_device_id)
else:
-42
View File
@@ -1,42 +0,0 @@
#!/usr/bin/env python3
# external modules
from os import listdir, remove, walk
from os.path import expanduser
from shutil import rmtree
# utility functions
def delete(_file_path, _target_database): # deletes an entry or folder, should be run remotely via ssh
try:
if _file_path.endswith('/'):
rmtree(f"{expanduser('~/.local/share/sshyp/')}{_file_path}")
else:
remove(f"{expanduser('~/.local/share/sshyp/')}{_file_path}.gpg")
except FileNotFoundError:
print(f"location does not exist {_target_database}")
for _device_name in listdir(expanduser('~/.config/sshyp/devices')):
open(f"{expanduser('~/.config/sshyp/deleted/')}{_file_path.replace('/', '@')}^&*{_device_name}", 'w')
def deletion_check(_client_device_name): # creates a list of entries and folders to be deleted from a local machine
open(expanduser('~/.config/sshyp/deletion_database'), 'w').write('')
for _file in listdir(expanduser('~/.config/sshyp/deleted')):
_file_path = _file.replace('@', '/').split('^&*')[0]
_device = _file.split('^&*')[1]
if _device == _client_device_name:
try:
remove(f"{expanduser('~/.config/sshyp/deleted/')}{_file}")
except FileNotFoundError:
pass
open(expanduser('~/.config/sshyp/deletion_database'), 'a').write(_file_path + '\n')
def folder_check(): # creates a list of folders to be compared with those of a local machine
open(expanduser('~/.config/sshyp/folder_database'), 'w').write('')
for _root, _directories, _files in walk(expanduser('~/.local/share/sshyp')):
for _dir in _directories:
open(expanduser('~/.config/sshyp/folder_database'), 'a')\
.write(f"{_root.replace(expanduser('~'), '')}/{_dir}\n")
+3 -5
View File
@@ -104,7 +104,6 @@ urls {
cp -r lib/. output/haikutemp/lib/sshyp/
sed -i '1 s/.*/#!\/bin\/env\ python3.10/' output/haikutemp/lib/sshyp/sshync.py
sed -i '1 s/.*/#!\/bin\/env\ python3.10/' output/haikutemp/lib/sshyp/sshyp.py
sed -i '1 s/.*/#!\/bin\/env\ python3.10/' output/haikutemp/lib/sshyp/sshypRemote.py
ln -s /system/lib/sshyp/sshyp.py output/haikutemp/bin/sshyp
cp -r share/doc/sshyp/. output/haikutemp/documentation/packages/sshyp/
cp -r share/licenses/sshyp/. output/haikutemp/documentation/packages/sshyp/
@@ -196,11 +195,11 @@ cp -r %{_sourcedir}/usr %{buildroot}
/usr/bin/sshyp
/usr/lib/sshyp/sshyp.py
/usr/lib/sshyp/sshync.py
/usr/lib/sshyp/sshypRemote.py
/usr/share/bash-completion/completions/sshyp
%license /usr/share/licenses/sshyp/license
%changelog /usr/share/doc/sshyp/changelog
%doc /usr/share/man/man1/sshyp.1.gz
%doc
/usr/share/doc/sshyp/changelog
/usr/share/man/man1/sshyp.1.gz
" > ~/rpmbuild/SPECS/sshyp.spec
rpmbuild -bb ~/rpmbuild/SPECS/sshyp.spec
mv ~/rpmbuild/RPMS/noarch/* output/
@@ -239,7 +238,6 @@ prefix: /
echo "/usr/bin/sshyp
/usr/lib/sshyp/sshync.py
/usr/lib/sshyp/sshyp.py
/usr/lib/sshyp/sshypRemote.py
/usr/share/bash-completion/completions/sshyp
/usr/share/doc/sshyp/changelog
/usr/share/licenses/sshyp/license
+32 -39
View File
@@ -1,55 +1,48 @@
sshyp v1.2.0
sshyp v1.3.0
the brisk bahh update
the serious shepherd update
this release focuses on speeding up the sshyp user experience by adding
new features that reduce wasted time
this release ties up many of sshyp's loose ends where there was
room for major performance, compatibility, and security improvements
compatibility-breaking changes:
- new configuration options (quick-unlock, offline mode) have been added and
the configuration files have been reorganized
^ simply running "sshyp tweak" and following the setup wizard will correct any compatibility
issues
- for quick-unlock security, device names have been replaced with more secure device ids
^ older device names are still compatible, but for security reasons it is recommended
to delete any pre-existing device names from the server and allow "sshyp tweak" to re-register
your devices
- due to a near full re-write of the syncing functionality, all clients and servers
must be updated to this release (v1.3.0 is not backwards compatible with any prior release)
- it is recommended to either delete the contents of ~/.config/sshyp/deleted (on the server-side)
or sync all of your clients before updating
^ old entries in this folder will throw errors with v1.3.0
user-facing features:
- quick-unlock mode has been added
^ this allows you to use a shortened version of your password by verifying that your device
is whitelisted on your sshyp server - it's both faster and more secure than standard unlock,
but it requires an active connection to your sshyp server to authenticate (otherwise it will
fall back to standard unlock)
- full support for offline usage
^ though sshyp could be used without a server before, it now can be configured to not attempt
to find one ever - this saves time and hides sync failure error messages
- bash completions have been added
^ if you have bash-completion installed, you can now use the tab key in bash to auto-complete
sshyp arguments and entry names (client only, not added for server-specific arguments)
^ if you do not have bash-completion installed, you can source
/usr/share/bash-completion/completions/sshyp (Linux/BSD) or
/boot/system/data/bash-completion/completions/sshyp (Haiku) in your ~/.bashrc to
use this feature
- none - all changes were under-the-hood - the user experience should be
exactly the same as v1.2.0 - just faster, less buggy, and more secure
fixes/optimizations:
major fixes/optimizations:
- fixed entries with multi-word titles failing to decrypt
- password generation is now much faster and more resource efficient
- there is no longer a length limit on generated passwords
- improved visual consistency of help menus
- rarely used modules are now imported only when needed
- sshyp now uses one fewer configuration file
- a near full re-write of the syncing functionality
^ all syncing logic has been moved into sshync.py (from sshyp.py and sshypRemote.py)
^ in my setup, a dry, local "sshyp sync" went from 2.00+ seconds (v1.2.0) to 0.36 seconds (v1.3.0)
^ the performance improvements are even greater when syncing from outside your local network
- the following character sequences will no longer break the syncing logic: "@", "^&*", and "*&^"
^ ASCII separator characters 29-31 are now used, instead
- os.system has been replaced with subprocess.run in all cases, shell=True is no longer used with subprocess.run
^ this protects against shell escape attacks and potentially makes sshyp more compatible with some environments
- replaced shell commands with python built-in library functions where applicable
^ this brings speed and compatibility improvements
- sshyp should no longer incorrectly assume an X11 environment when Wayland is in use
^ this fixes clipboard support in some Wayland environments, such as Sway (Plasma/Gnome/Phosh were unaffected)
- sshyp now uses the default pinentry on Haiku thanks to haikuports/haikuports#7457
^ this brings the Haiku port in-line with the other sshyp packages in terms of security
- "python3" is now called over ssh, rather than "python"
^ some environments do not have a "python" symlink, or it links to "python2" - changing this increases compatibility
- fixed an issue from v1.2.0 where renaming threw an error if not in offline mode
other notable changes:
- sshyp is now specifically licensed under the GPL-3.0-only (keyword: only)
- sshyp now has some possible arguments and its own help menu when running in server mode
- temporary files in /dev/shm are now generated with more complex names
- sshyp now installs in /usr/lib/sshyp (Linux/BSD) or /system/lib/sshyp (Haiku) instead of
/usr/bin or /bin (it is still symlinked to the old directories)
- quick-unlock password input is now hidden while the user is typing
^ user input is now invisible to prevent snooping
- lots of smaller optimizations not listed here
<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>