mirror of
https://github.com/rwinkhart/sshyp.git
synced 2026-09-03 07:37:16 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
921630f5a8 | ||
|
|
627c6a1a14 | ||
|
|
a9f4435493 | ||
|
|
7d639ecca9 | ||
|
|
eefe5e39b5 | ||
|
|
c28b836c2a | ||
|
|
b53370a3e2 | ||
|
|
9776961f2a | ||
|
|
1a35aaf33b | ||
|
|
66b15ac22c | ||
|
|
56db2e65f8 | ||
|
|
1be67d404b | ||
|
|
a279819260 | ||
|
|
578f7dee14 | ||
|
|
5bec4fdaa9 | ||
|
|
5f903966da | ||
|
|
cfd551ef7c | ||
|
|
4d22b5b5a8 | ||
|
|
46d5f8d515 | ||
|
|
02ee2a5c0e | ||
|
|
fb1c3844a8 | ||
|
|
213ca41582 | ||
|
|
b8dc5c4f89 | ||
|
|
574637392a | ||
|
|
c6f31da6d4 | ||
|
|
0f4c57bec3 | ||
|
|
51b1b8c6ec | ||
|
|
5f0a40efc0 | ||
|
|
05c034652f | ||
|
|
1d185b1723 | ||
|
|
bca2af0ed8 | ||
|
|
d301cf6298 | ||
|
|
89c9a03cc1 | ||
|
|
3392e3ceea | ||
|
|
176ee623cf | ||
|
|
42abb7b674 |
@@ -1,5 +1,9 @@
|
|||||||

|

|
||||||
|
|
||||||
|
[](https://github.com/rwinkhart/sshyp/releases)
|
||||||
|

|
||||||
|
[](https://github.com/rwinkhart/sshyp/releases)
|
||||||
|
|
||||||
[](https://github.com/rwinkhart/sshyp/actions/workflows/codeql-analysis.yml)
|
[](https://github.com/rwinkhart/sshyp/actions/workflows/codeql-analysis.yml)
|
||||||
|
|
||||||
pronounced as: 'sheep', 'shēp'
|
pronounced as: 'sheep', 'shēp'
|
||||||
@@ -91,9 +95,8 @@ man sshyp
|
|||||||
# Roadmap
|
# Roadmap
|
||||||
Short-term Goals:
|
Short-term Goals:
|
||||||
|
|
||||||
- create minimal GUI app (Linux x86_64, Linux aarch64) - being done as an [extension](https://github.com/rwinkhart/sshyp-labs)
|
- a minimal GUI app - being made as an [extension](https://github.com/rwinkhart/sshyp-labs)
|
||||||
- significant optimizations
|
- improved extension integration (allow extensions to add new arguments)
|
||||||
- vaious bug fixes
|
|
||||||
|
|
||||||
Long-term Goals:
|
Long-term Goals:
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
sshyp v1.2.0
|
||||||
|
|
||||||
the brisk bahh update
|
the brisk bahh update
|
||||||
|
|||||||
+2
-6
@@ -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
|
.SH NAME
|
||||||
sshyp \- A very simple self-hosted, synchronized password manager for UNIX(-like) systems. Alternative to (and compatible with) pass/password-store.
|
sshyp \- A very simple self-hosted, synchronized password manager for UNIX(-like) systems. Alternative to (and compatible with) pass/password-store.
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
@@ -88,10 +88,6 @@ whitelist:
|
|||||||
list/-l view all registered device ids and their quick-unlock whitelist status
|
list/-l view all registered device ids and their quick-unlock whitelist status
|
||||||
add whitelist a device id for quick-unlock
|
add whitelist a device id for quick-unlock
|
||||||
delete/del remove a device id from the quick-unlock whitelist
|
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
|
.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.
|
||||||
|
|
||||||
@@ -127,6 +123,6 @@ Please note that the server setup intentionally does not allow the reading of en
|
|||||||
|
|
||||||
5 - decryption/encryption error
|
5 - decryption/encryption error
|
||||||
|
|
||||||
6 - server data retrieval error
|
6 - server connection error
|
||||||
.SH AUTHOR
|
.SH AUTHOR
|
||||||
Randall Winkhart (https://github.com/rwinkhart)
|
Randall Winkhart (https://github.com/rwinkhart)
|
||||||
|
|||||||
+112
-48
@@ -1,45 +1,100 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
from os import listdir, remove, walk
|
||||||
# external modules
|
|
||||||
|
|
||||||
from os import path, system, walk
|
|
||||||
from os.path import expanduser, getmtime, join
|
from os.path import expanduser, getmtime, join
|
||||||
|
from subprocess import CalledProcessError, PIPE, run
|
||||||
from sys import exit as s_exit
|
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
|
def remote_list_gen(_client_device_name, _remote_dir): # prints all necessary remote data to stdout
|
||||||
from pathlib import Path
|
# 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 = [], []
|
_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 _root, _directories, _files in walk(_directory):
|
||||||
for _filename in _files:
|
for _filename in _files:
|
||||||
_title_list.append(join(_root.replace(_directory, '', 1), _filename))
|
_title_list.append(join(_root.replace(_directory, '', 1), _filename))
|
||||||
_mod_list.append(int(getmtime(join(_root, _filename))))
|
_mod_list.append(int(getmtime(join(_root, _filename))))
|
||||||
|
if _device == 'server':
|
||||||
for _title in _title_list:
|
for _title in _title_list:
|
||||||
open(expanduser('~/.config/sshync/database'), 'a').write(str(_title) + '\n')
|
print(_title.rstrip())
|
||||||
open(expanduser('~/.config/sshync/database'), 'a').write('^&*\n')
|
for _time in _mod_list:
|
||||||
for _mod in _mod_list:
|
print(_time)
|
||||||
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:]
|
|
||||||
return _title_list, _mod_list
|
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
|
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_list_2_sorted, _mod_list_2_sorted = [], []
|
||||||
# title sorting
|
# title sorting
|
||||||
for _title in _list_1[0]:
|
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:
|
try:
|
||||||
_profile_data = open(_profile_dir).readlines()
|
_profile_data = open(_profile_dir).readlines()
|
||||||
except (FileNotFoundError, IndexError):
|
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
|
_profile_data = None
|
||||||
s_exit(3)
|
s_exit(3)
|
||||||
# extract data from profile
|
|
||||||
_user = _profile_data[0].rstrip()
|
_user = _profile_data[0].rstrip()
|
||||||
_ip = _profile_data[1].rstrip()
|
_ip = _profile_data[1].rstrip()
|
||||||
_port = _profile_data[2].rstrip()
|
_port = _profile_data[2].rstrip()
|
||||||
_local_dir = _profile_data[3].rstrip()
|
_local_dir = _profile_data[3].rstrip()
|
||||||
_remote_dir = _profile_data[4].rstrip()
|
_remote_dir = _profile_data[4].rstrip()
|
||||||
_identity = _profile_data[5].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
|
def run_profile(_profile_dir, _silent): # runs a sshync job profile
|
||||||
_user_data = get_profile(_profile_dir)
|
_user_data = get_profile(_profile_dir) # import profile data
|
||||||
_remote_titles_mods_saver = get_titles_mods(_user_data[4], 'r', _user_data) # saved to prevent re-walking directory
|
# fetch remote lists
|
||||||
_index_l = sort_titles_mods(_remote_titles_mods_saver, get_titles_mods(_user_data[3], 'l', _user_data))
|
_deletion_database, _folder_database, _remote_titles, _remote_mods = remote_list_fetch(_user_data)
|
||||||
_index_r = sort_titles_mods(_index_l, _remote_titles_mods_saver)
|
_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
|
_i = -1
|
||||||
for _title in _index_l[0]:
|
for _title in _index_local[0]:
|
||||||
_i += 1
|
_i += 1
|
||||||
if _title in _index_r[0]:
|
if _title in _index_remote[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_local[1][_i]) > int(_index_remote[1][_i]):
|
||||||
print(f"\u001b[38;5;4m{_title[:-4]}\u001b[0m is newer locally, uploading...")
|
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}' "
|
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]) + '/'}'")
|
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_local[1][_i]) < int(_index_remote[1][_i]):
|
||||||
print(f"\u001b[38;5;2m{_title[:-4]}\u001b[0m is newer remotely, downloading...")
|
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]}:"
|
run(['scp', '-pqs', '-P', _user_data[2], '-i', _user_data[5],
|
||||||
f"{_user_data[4]}{_title}' '{_user_data[3]}{'/'.join(_title.split('/')[:-1]) + '/'}'")
|
f"{_user_data[0]}@{_user_data[1]}:{_user_data[4]}{_title}",
|
||||||
|
f"{_user_data[3]}{'/'.join(_title.split('/')[:-1]) + '/'}"])
|
||||||
else:
|
else:
|
||||||
print(f"\u001b[38;5;4m{_title[:-4]}\u001b[0m is not on remote server, uploading...")
|
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}' "
|
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]) + '/'}'")
|
f"{_user_data[0]}@{_user_data[1]}:{_user_data[4]}{'/'.join(_title.split('/')[:-1]) + '/'}"])
|
||||||
for _title in _index_r[0]:
|
for _title in _index_remote[0]:
|
||||||
if _title not in _index_l[0]:
|
if _title not in _index_local[0]:
|
||||||
print(f"\u001b[38;5;2m{_title[:-4]}\u001b[0m is not in local directory, downloading...")
|
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]}:"
|
run(['scp', '-pqs', '-P', _user_data[2], '-i', _user_data[5],
|
||||||
f"{_user_data[4]}{_title}' '{_user_data[3]}{'/'.join(_title.split('/')[:-1]) + '/'}'")
|
f"{_user_data[0]}@{_user_data[1]}:{_user_data[4]}{_title}",
|
||||||
|
f"{_user_data[3]}{'/'.join(_title.split('/')[:-1]) + '/'}"])
|
||||||
|
|||||||
+160
-215
@@ -1,19 +1,17 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
from os import chmod, environ, listdir, remove, uname, walk
|
||||||
# external modules
|
from os.path import expanduser
|
||||||
|
|
||||||
from os import environ, listdir, path, remove, system, uname, walk
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from random import randint, SystemRandom
|
from random import randint
|
||||||
from shutil import get_terminal_size, move, rmtree
|
from shutil import get_terminal_size, move, rmtree
|
||||||
import sshync
|
from sshync import delete as offline_delete, run_profile, make_profile, get_profile
|
||||||
from subprocess import CalledProcessError, Popen, PIPE, run
|
from subprocess import CalledProcessError, DEVNULL, PIPE, run
|
||||||
from sys import argv, exit as s_exit
|
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
|
from textwrap import fill
|
||||||
print('\n\u001b[38;5;0;48;5;15msshyp entries:\u001b[0m\n')
|
print('\n\u001b[38;5;0;48;5;15msshyp entries:\u001b[0m\n')
|
||||||
_entry_list, _color_alternator = [], 1
|
_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')
|
print(fill(' '.join(_entry_list), width=_width) + '\n')
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
for _root, _directories, _files in walk(_directory):
|
for _root, _dirs, _files in walk(_directory):
|
||||||
for _dir in sorted(_directories):
|
for _dir in sorted(_dirs):
|
||||||
_inner_dir = f"{_root.replace(_directory, '')}/{_dir}"
|
_inner_dir = f"{_root.replace(_directory, '')}/{_dir}"
|
||||||
print(f"\u001b[38;5;15;48;5;238m{_inner_dir}/\u001b[0m")
|
print(f"\u001b[38;5;15;48;5;238m{_inner_dir}/\u001b[0m")
|
||||||
_entry_list, _color_alternator = [], 1
|
_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('/'):
|
if _entry_name.startswith('/'):
|
||||||
return _entry_name.replace('/', '', 1)
|
return _entry_name.replace('/', '', 1)
|
||||||
else:
|
else:
|
||||||
return _entry_name
|
return _entry_name.strip()
|
||||||
|
|
||||||
|
|
||||||
def string_gen(_complexity, _length): # generates and returns a random string based on input
|
def string_gen(_complexity, _length): # generates and returns a random string based on input
|
||||||
|
from random import SystemRandom
|
||||||
import string
|
import string
|
||||||
if _complexity == 's':
|
if _complexity == 's':
|
||||||
_character_pool = string.ascii_letters + string.digits
|
_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
|
def pass_gen(): # prompts the user for necessary information to generate a password and passes it to string_gen
|
||||||
|
_length = 9
|
||||||
|
while True:
|
||||||
try:
|
try:
|
||||||
_length = int(input('password length: '))
|
_length = int(input('password length: '))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
print(f"\n\u001b[38;5;9merror: a non-integer value was input for password length\u001b[0m\n")
|
continue
|
||||||
_gen = pass_gen()
|
else:
|
||||||
return _gen
|
if _length < 1:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
break
|
||||||
_complexity = str(input('password complexity - simple (for compatibility) or complex (for security)? (s/C) '))
|
_complexity = str(input('password complexity - simple (for compatibility) or complex (for security)? (s/C) '))
|
||||||
if _complexity != 's' and _complexity != 'S':
|
if _complexity != 's' and _complexity != 'S':
|
||||||
_complexity = 'c'
|
_complexity = 'c'
|
||||||
@@ -132,39 +136,38 @@ def pass_gen(): # prompts the user for necessary information to generate a pass
|
|||||||
return _gen
|
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_folder_gen = string_gen('f', randint(12, 48))
|
||||||
_shm_entry_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
|
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
|
# 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")
|
move(f"{_tmp_dir}{_shm_folder}/{_shm_entry}.gpg", f"{_entry_dir}.gpg")
|
||||||
rmtree(f"{_tmp_dir}{_shm_folder}")
|
rmtree(f"{_tmp_dir}{_shm_folder}")
|
||||||
|
|
||||||
|
|
||||||
def decrypt(_entry_dir, _shm_folder, _shm_entry, _gpg_com, _quick_pass,
|
def decrypt(_entry_dir, _shm_folder, _shm_entry, _quick_pass,
|
||||||
_tmp_dir=path.expanduser('~/.config/sshyp/tmp/')): # decrypts an entry to a temporary directory
|
_tmp_dir=expanduser('~/.config/sshyp/tmp/')): # decrypts an entry to a temporary directory
|
||||||
if not isinstance(_quick_pass, bool):
|
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:
|
else:
|
||||||
_unlock_method = f"{_gpg_com} -qd --output "
|
_unlock_method = ['gpg', '-qd', '--output']
|
||||||
if _shm_folder == 0 and _shm_entry == 0:
|
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:
|
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:
|
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:
|
except CalledProcessError:
|
||||||
if not isinstance(_quick_pass, bool):
|
if not isinstance(_quick_pass, bool):
|
||||||
print('\n\u001b[38;5;9merror: quick-unlock failed as a result of an incorrect passphrase, an unreachable '
|
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')
|
'sshyp server, or an invalid configuration\n\nfalling back to standard unlock\u001b[0m\n')
|
||||||
try:
|
try:
|
||||||
run(f"{_gpg_com} -qd --output {_output_target}", shell=True, stderr=PIPE, check=True,
|
run(['gpg', '-qd', '--output'] + _output_target, stderr=DEVNULL, check=True)
|
||||||
close_fds=True)
|
|
||||||
except CalledProcessError:
|
except CalledProcessError:
|
||||||
print('\n\u001b[38;5;9merror: could not decrypt - ensure the correct gpg key is present\u001b[0m\n')
|
print('\n\u001b[38;5;9merror: could not decrypt - ensure the correct gpg key is present\u001b[0m\n')
|
||||||
s_exit(5)
|
s_exit(5)
|
||||||
@@ -173,12 +176,11 @@ def decrypt(_entry_dir, _shm_folder, _shm_entry, _gpg_com, _quick_pass,
|
|||||||
s_exit(5)
|
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':
|
if quick_unlock_enabled == 'y':
|
||||||
decrypt(_entry_dir, _shm_folder, _shm_entry, _gpg_com, whitelist_verify(port, username_ssh, ip,
|
decrypt(_entry_dir, _shm_folder, _shm_entry, whitelist_verify(port, username_ssh, ip, client_device_id))
|
||||||
client_device_id))
|
|
||||||
else:
|
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
|
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
|
def edit_note(_shm_folder, _shm_entry, _lines): # edits the note attached to an entry
|
||||||
_reg_lines = _lines[0:3]
|
_reg_lines = _lines[0:3]
|
||||||
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'w').writelines(_lines[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()
|
_new_notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n").readlines()
|
||||||
while len(_reg_lines) < 3:
|
while len(_reg_lines) < 3:
|
||||||
_reg_lines += ['\n']
|
_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):
|
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
|
# 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:
|
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:
|
except CalledProcessError:
|
||||||
print('\n\u001b[38;5;9mwarning: ssh connection could not be made - ensure the public key (~/.ssh/sshyp.pub) is '
|
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 '
|
'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')
|
'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
|
return 1
|
||||||
open(path.expanduser('~/.config/sshyp/ssh-error'), 'w').write('0')
|
open(expanduser('~/.config/sshyp/ssh-error'), 'w').write('0')
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
# BELOW - argument-specific functions
|
|
||||||
|
|
||||||
|
# ARGUMENT-SPECIFIC FUNCTIONS
|
||||||
|
|
||||||
def tweak(): # runs configuration wizard
|
def tweak(): # runs configuration wizard
|
||||||
|
from os import symlink
|
||||||
_divider = f"\n{'=' * (get_terminal_size()[0] - int((.5 * get_terminal_size()[0])))}\n\n"
|
_divider = f"\n{'=' * (get_terminal_size()[0] - int((.5 * get_terminal_size()[0])))}\n\n"
|
||||||
|
|
||||||
# config directory creation
|
# config directory creation
|
||||||
Path(path.expanduser('~/.config/sshyp/devices')).mkdir(0o700, parents=True, exist_ok=True)
|
Path(expanduser('~/.config/sshyp/devices')).mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||||
if not Path(f"{path.expanduser('~/.config/sshyp/tmp')}").exists():
|
if not Path(f"{expanduser('~/.config/sshyp/tmp')}").exists():
|
||||||
if uname()[0] == 'Haiku' or uname()[0] == 'FreeBSD':
|
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():
|
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:
|
else:
|
||||||
system(f"ln -s /dev/shm {path.expanduser('~/.config/sshyp/tmp')}")
|
symlink('/dev/shm', expanduser('~/.config/sshyp/tmp'))
|
||||||
|
|
||||||
# device type configuration
|
# device type configuration
|
||||||
_device_type = input('\nclient or server installation? (C/s) ')
|
_device_type = input('\nclient or server installation? (C/s) ')
|
||||||
if _device_type.lower() == 's':
|
if _device_type.lower() == 's':
|
||||||
_sshyp_data = ['server']
|
_sshyp_data = ['server']
|
||||||
Path(path.expanduser('~/.config/sshyp/deleted')).mkdir(0o700, exist_ok=True)
|
Path(expanduser('~/.config/sshyp/deleted')).mkdir(mode=0o700, exist_ok=True)
|
||||||
Path(path.expanduser('~/.config/sshyp/whitelist')).mkdir(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")
|
print(f"\n\u001b[4;1mmake sure the ssh service is running and properly configured\u001b[0m")
|
||||||
else:
|
else:
|
||||||
_sshyp_data = ['client']
|
_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 configuration
|
||||||
_gpg_gen = input(f"{_divider}sshyp requires the use of a unique gpg key - use an (e)xisting key or (g)enerate a"
|
_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) ")
|
f" new one? (E/g) ")
|
||||||
if _gpg_gen.lower() != 'g':
|
if _gpg_gen.lower() != 'g':
|
||||||
system(f"{gpg} -k")
|
run(['gpg', '-k'])
|
||||||
_sshyp_data += [str(input('gpg key id: '))]
|
_sshyp_data += [str(input('gpg key id: '))]
|
||||||
else:
|
else:
|
||||||
print('\na unique gpg key is being generated for you...')
|
print('\na unique gpg key is being generated for you...')
|
||||||
if not Path(path.expanduser('~/.config/sshyp/gpg-gen')).is_file():
|
if not Path(expanduser('~/.config/sshyp/gpg-gen')).is_file():
|
||||||
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',
|
'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'])
|
'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', expanduser('~/.config/sshyp/gpg-gen')])
|
||||||
run(gpg + ' --batch --generate-key --passphrase ' + "'" +
|
remove(expanduser('~/.config/sshyp/gpg-gen'))
|
||||||
input('\ngpg passphrase: ') + "'" + " '" +
|
_sshyp_data += [run(['gpg', '-k'], stdout=PIPE, text=True).stdout.split('\n')[-4].strip()]
|
||||||
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()]
|
|
||||||
|
|
||||||
# text editor configuration
|
# text editor configuration
|
||||||
_sshyp_data += [input(f"{_divider}example input: vim\n\npreferred text editor: ")]
|
_sshyp_data += [input(f"{_divider}example input: vim\n\npreferred text editor: ")]
|
||||||
|
|
||||||
# lock file generation
|
# lock file generation
|
||||||
if Path(path.expanduser('~/.config/sshyp/lock.gpg')).is_file():
|
if Path(expanduser('~/.config/sshyp/lock.gpg')).is_file():
|
||||||
remove(path.expanduser('~/.config/sshyp/lock.gpg'))
|
remove(expanduser('~/.config/sshyp/lock.gpg'))
|
||||||
open(path.expanduser('~/.config/sshyp/lock'), 'w')
|
open(expanduser('~/.config/sshyp/lock'), 'w')
|
||||||
system(f"{gpg} -qr {str(_sshyp_data[1])} -e {path.expanduser('~/.config/sshyp/lock')}")
|
run(['gpg', '-qr', str(_sshyp_data[1]), '-e', expanduser('~/.config/sshyp/lock')])
|
||||||
remove(path.expanduser('~/.config/sshyp/lock'))
|
remove(expanduser('~/.config/sshyp/lock'))
|
||||||
|
|
||||||
# ssh key configuration
|
# ssh key configuration
|
||||||
_offline_mode = False
|
_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"configured\n\nsync support requires a unique ssh key - would you like to have this "
|
||||||
f"automatically generated? (Y/n/o(ffline)) "))
|
f"automatically generated? (Y/n/o(ffline)) "))
|
||||||
if _ssh_gen.lower() != 'n' and _ssh_gen.lower() != 'o' and _ssh_gen.lower() != 'offline':
|
if _ssh_gen.lower() != 'n' and _ssh_gen.lower() != 'o' and _ssh_gen.lower() != 'offline':
|
||||||
if uname()[0] == 'Haiku':
|
Path(f"{expanduser('~')}/.ssh").mkdir(mode=0o700, exist_ok=True)
|
||||||
Path(f"{path.expanduser('~')}/.ssh").mkdir(0o700, exist_ok=True)
|
run(['ssh-keygen', '-t', 'ed25519', '-f', expanduser('~/.ssh/sshyp')])
|
||||||
system('ssh-keygen -t ed25519 -f ~/.ssh/sshyp')
|
|
||||||
elif _ssh_gen.lower() == 'n':
|
elif _ssh_gen.lower() == 'n':
|
||||||
print(f"\n\u001b[4;1mensure that the key file you are using is located at "
|
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':
|
elif _ssh_gen.lower() == 'o' or _ssh_gen.lower() == 'offline':
|
||||||
_offline_mode = True
|
_offline_mode = True
|
||||||
print('\nsshyp has been set to offline mode - to enable syncing, run "sshyp tweak" again')
|
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: '))
|
_username_ssh = str(input('\nusername of the remote server: '))
|
||||||
|
|
||||||
# sshync profile generation
|
# sshync profile generation
|
||||||
sshync.make_profile(path.expanduser('~/.config/sshyp/sshyp.sshync'),
|
make_profile(expanduser('~/.config/sshyp/sshyp.sshync'),
|
||||||
path.expanduser('~/.local/share/sshyp/'), f"/home/{_username_ssh}/.local/share/sshyp/",
|
expanduser('~/.local/share/sshyp/'), f"/home/{_username_ssh}/.local/share/sshyp/",
|
||||||
path.expanduser('~/.ssh/sshyp'), _ip, _port, _username_ssh)
|
expanduser('~/.ssh/sshyp'), _ip, _port, _username_ssh)
|
||||||
|
|
||||||
# device id configuration
|
# device id configuration
|
||||||
for _id in listdir(path.expanduser('~/.config/sshyp/devices')): # remove existing device id
|
for _id in listdir(expanduser('~/.config/sshyp/devices')): # remove existing device id
|
||||||
remove(f"{path.expanduser('~/.config/sshyp/devices/')}{_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 "
|
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")
|
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_prefix = str(input('device id: ')) + '-'
|
||||||
_device_id_suffix = string_gen('f', randint(24, 48))
|
_device_id_suffix = string_gen('f', randint(24, 48))
|
||||||
_device_id = _device_id_prefix + _device_id_suffix
|
_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
|
# quick-unlock configuration
|
||||||
print(f"{_divider}this allows you to use a shorter version of your gpg key password and\n"
|
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
|
# test server connection and attempt to register device id
|
||||||
copy_id_check(_port, _username_ssh, _ip, _device_id)
|
copy_id_check(_port, _username_ssh, _ip, _device_id)
|
||||||
|
|
||||||
elif Path(path.expanduser('~/.config/sshyp/sshyp.sshync')).is_file():
|
elif Path(expanduser('~/.config/sshyp/sshyp.sshync')).is_file():
|
||||||
remove(path.expanduser('~/.config/sshyp/sshyp.sshync'))
|
remove(expanduser('~/.config/sshyp/sshyp.sshync'))
|
||||||
|
|
||||||
# write main config file (sshyp-data)
|
# 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
|
_lines = 0
|
||||||
for _item in _sshyp_data:
|
for _item in _sshyp_data:
|
||||||
_lines += 1
|
_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':
|
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/'
|
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')
|
'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 : *"
|
"♥♥♥♥♥♥♥\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 // \\\\")
|
"\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[0m')
|
||||||
print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m '
|
print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m '
|
||||||
@@ -413,9 +410,9 @@ 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')
|
'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 '
|
print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m '
|
||||||
'\u001b[38;5;7;48;5;8m/\u001b[0m')
|
'\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')
|
'\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 '
|
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')
|
'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 '
|
print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m '
|
||||||
'\u001b[38;5;7;48;5;8m/\u001b[0m')
|
'\u001b[38;5;7;48;5;8m/\u001b[0m')
|
||||||
@@ -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")
|
print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) does not exist\u001b[0m\n")
|
||||||
s_exit(3)
|
s_exit(3)
|
||||||
_shm_folder, _shm_entry = shm_gen()
|
_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}")
|
entry_reader(f"{tmp_dir}{_shm_folder}/{_shm_entry}")
|
||||||
rmtree(f"{tmp_dir}{_shm_folder}")
|
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")
|
print(f"\n\u001b[38;5;9merror: entry ({argument.replace('/', '', 1)}) does not exist\u001b[0m\n")
|
||||||
s_exit(3)
|
s_exit(3)
|
||||||
_shm_folder, _shm_entry = shm_gen()
|
_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}")
|
entry_reader(f"{tmp_dir}{_shm_folder}/{_shm_entry}")
|
||||||
rmtree(f"{tmp_dir}{_shm_folder}")
|
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
|
|
||||||
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
|
# set permissions before uploading
|
||||||
system('find ' + directory + ' -type d -exec chmod -R 700 {} +')
|
for _root, _dirs, _files in walk(expanduser('~/.local/share/sshyp')):
|
||||||
system('find ' + directory + ' -type f -exec chmod -R 600 {} +')
|
for _path in _root.split('\n'):
|
||||||
sshync.run_profile(path.expanduser('~/.config/sshyp/sshyp.sshync'))
|
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
|
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
|
_i += 1
|
||||||
|
|
||||||
# create assembly key
|
# 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',
|
'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'])
|
'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 + "'" +
|
run(['gpg', '-q', '--pinentry-mode', 'loopback', '--batch', '--generate-key', '--passphrase',
|
||||||
" '" + path.expanduser('~/.config/sshyp/gpg-gen') + "'", shell=True)
|
_quick_unlock_password, expanduser('~/.config/sshyp/gpg-gen')])
|
||||||
remove(path.expanduser('~/.config/sshyp/gpg-gen'))
|
remove(expanduser('~/.config/sshyp/gpg-gen'))
|
||||||
_gpg_id = run(f"{gpg} -k", shell=True, stdout=PIPE, text=True).stdout.split('\n')[-4].strip()
|
_gpg_id = run(['gpg', '-k'], stdout=PIPE, text=True).stdout.split('\n')[-4].strip()
|
||||||
|
|
||||||
# encrypt excluded with the assembly key
|
# encrypt excluded with the assembly key
|
||||||
_shm_folder, _shm_entry = shm_gen()
|
_shm_folder, _shm_entry = shm_gen()
|
||||||
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w').write(_quick_unlock_password_excluded)
|
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}")
|
print(f"\nyour quick-unlock passphrase: {_quick_unlock_password}")
|
||||||
|
|
||||||
|
|
||||||
def whitelist_verify(_port, _username_ssh, _ip, _client_device_id):
|
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
|
# checks the user's whitelist status and fetches the full gpg key password if possible
|
||||||
try:
|
try:
|
||||||
run(f"gpg --pinentry-mode cancel -qd --output /dev/null {path.expanduser('~/.config/sshyp/lock.gpg')}",
|
run(['gpg', '--pinentry-mode', 'cancel', '-qd', '--output', '/dev/null',
|
||||||
shell=True, stderr=PIPE, check=True, close_fds=True)
|
expanduser('~/.config/sshyp/lock.gpg')], stderr=DEVNULL, check=True)
|
||||||
return False
|
return False
|
||||||
except CalledProcessError:
|
except CalledProcessError:
|
||||||
_i, _full_password = 0, ''
|
_i, _full_password = 0, ''
|
||||||
_server_whitelist = run('ssh -i ' + "'" + path.expanduser('~/.ssh/sshyp') + "' -p " + _port + " " +
|
_server_whitelist = run(['ssh', '-i', expanduser('~/.ssh/sshyp'), '-p', _port, f"{_username_ssh}@{_ip}",
|
||||||
_username_ssh + '@' + _ip + " 'ls ~/.config/sshyp/whitelist'",
|
f'python3 -c \'from os import listdir; print(*listdir("/home/{_username_ssh}'
|
||||||
shell=True, stdout=PIPE, text=True)
|
f'/.config/sshyp/whitelist"))\''], stdout=PIPE, text=True).stdout.rstrip().split(' ')
|
||||||
for _device_id in _server_whitelist.stdout.rstrip().split('\n'):
|
for _device_id in _server_whitelist:
|
||||||
if _device_id == _client_device_id:
|
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 = \
|
_quick_unlock_password_excluded = \
|
||||||
run('ssh -i ' + "'" + path.expanduser('~/.ssh/sshyp') + "' -p " + _port + " " + _username_ssh + '@'
|
run(['ssh', '-i', expanduser('~/.ssh/sshyp'), '-p', _port, f"{_username_ssh}@{_ip}",
|
||||||
+ _ip + f" 'gpg --pinentry-mode loopback --passphrase '{_quick_unlock_password}' -qd"
|
f"gpg --pinentry-mode loopback --passphrase '{_quick_unlock_password}' "
|
||||||
f" ~/.config/sshyp/excluded.gpg'", shell=True, stdout=PIPE, text=True).stdout.rstrip()
|
f"-qd ~/.config/sshyp/excluded.gpg"], stdout=PIPE, text=True).stdout.rstrip()
|
||||||
while _i < len(_quick_unlock_password_excluded):
|
while _i < len(_quick_unlock_password_excluded):
|
||||||
try:
|
try:
|
||||||
_full_password += _quick_unlock_password_excluded[_i]
|
_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
|
def whitelist_list(): # shows the quick-unlock whitelist status of device ids
|
||||||
_whitelisted_ids = listdir(path.expanduser('~/.config/sshyp/whitelist'))
|
_whitelisted_ids = listdir(expanduser('~/.config/sshyp/whitelist'))
|
||||||
_device_ids = listdir(path.expanduser('~/.config/sshyp/devices'))
|
_device_ids = listdir(expanduser('~/.config/sshyp/devices'))
|
||||||
print('\n\u001b[1mquick-unlock whitelisted device ids:\u001b[0m')
|
print('\n\u001b[1mquick-unlock whitelisted device ids:\u001b[0m')
|
||||||
for _id in _whitelisted_ids:
|
for _id in _whitelisted_ids:
|
||||||
print(_id)
|
print(_id)
|
||||||
@@ -624,15 +585,15 @@ def whitelist_manage(): # adds or removes quick-unlock whitelisted device ids
|
|||||||
_device_id = ' '.join(_argument_split)
|
_device_id = ' '.join(_argument_split)
|
||||||
|
|
||||||
if argument_list[2] == 'add':
|
if argument_list[2] == 'add':
|
||||||
if _device_id in listdir(path.expanduser('~/.config/sshyp/devices')):
|
if _device_id in listdir(expanduser('~/.config/sshyp/devices')):
|
||||||
open(path.expanduser(f"~/.config/sshyp/whitelist/{_device_id}"), 'w').write('')
|
open(expanduser(f"~/.config/sshyp/whitelist/{_device_id}"), 'w').write('')
|
||||||
whitelist_list()
|
whitelist_list()
|
||||||
else:
|
else:
|
||||||
print(f"\n\u001b[38;5;9merror: device id ({_device_id}) is not registered\u001b[0m\n")
|
print(f"\n\u001b[38;5;9merror: device id ({_device_id}) is not registered\u001b[0m\n")
|
||||||
s_exit(2)
|
s_exit(2)
|
||||||
|
|
||||||
elif Path(path.expanduser(f"~/.config/sshyp/whitelist/{_device_id}")).is_file():
|
elif Path(expanduser(f"~/.config/sshyp/whitelist/{_device_id}")).is_file():
|
||||||
remove(path.expanduser(f"~/.config/sshyp/whitelist/{_device_id}"))
|
remove(expanduser(f"~/.config/sshyp/whitelist/{_device_id}"))
|
||||||
whitelist_list()
|
whitelist_list()
|
||||||
|
|
||||||
|
|
||||||
@@ -647,7 +608,7 @@ def add_entry(): # adds a new entry
|
|||||||
s_exit(4)
|
s_exit(4)
|
||||||
if argument_list[2] == 'note' or argument_list[2] == '-n':
|
if argument_list[2] == 'note' or argument_list[2] == '-n':
|
||||||
_shm_folder, _shm_entry = shm_gen()
|
_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()
|
_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))
|
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':
|
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) ')
|
_add_note = input('add a note to this entry? (y/N) ')
|
||||||
_shm_folder, _shm_entry = shm_gen()
|
_shm_folder, _shm_entry = shm_gen()
|
||||||
if _add_note.lower() == 'y':
|
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()
|
_notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'r').read()
|
||||||
else:
|
else:
|
||||||
_notes = ''
|
_notes = ''
|
||||||
@@ -665,7 +626,7 @@ def add_entry(): # adds a new entry
|
|||||||
.writelines(optimized_edit([_password, _username, _url, _notes], None, -1))
|
.writelines(optimized_edit([_password, _username, _url, _notes], None, -1))
|
||||||
print('\n\u001b[1mentry preview:\u001b[0m')
|
print('\n\u001b[1mentry preview:\u001b[0m')
|
||||||
entry_reader(f"{tmp_dir}{_shm_folder}/{_shm_entry}")
|
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
|
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: ')
|
_entry_name = entry_name_fetch('name of new folder: ')
|
||||||
else:
|
else:
|
||||||
_entry_name = entry_name_fetch(2)
|
_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:
|
if ssh_error != 1:
|
||||||
system(f"ssh -i '{path.expanduser('~/.ssh/sshyp')}' -p {port} {username_ssh}@{ip} \"mkdir -p "
|
run(['ssh', '-i', expanduser('~/.ssh/sshyp'), '-p', port, f"{username_ssh}@{ip}",
|
||||||
f"'{directory_ssh}{_entry_name}'\"")
|
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
|
def rename(): # renames an entry or folder
|
||||||
|
from shutil import copy
|
||||||
if argument == 'edit rename' or argument == 'edit relocate' or argument == 'edit -r':
|
if argument == 'edit rename' or argument == 'edit relocate' or argument == 'edit -r':
|
||||||
_entry_name = entry_name_fetch('entry/folder to rename/relocate: ')
|
_entry_name = entry_name_fetch('entry/folder to rename/relocate: ')
|
||||||
else:
|
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")
|
print(f"\n\u001b[38;5;9merror: ({_new_name}) already exists\u001b[0m\n")
|
||||||
s_exit(4)
|
s_exit(4)
|
||||||
if _entry_name.endswith('/'):
|
if _entry_name.endswith('/'):
|
||||||
move(f"{directory}{_entry_name}", f"{directory}{_new_name}")
|
|
||||||
if ssh_error != 1:
|
if ssh_error != 1:
|
||||||
system(f"ssh -i '{path.expanduser('~/.ssh/sshyp')}' -p {port} {username_ssh}@{ip} \"mkdir -p "
|
Path(f"{directory}{_new_name}").mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||||
f"'{directory_ssh}{_new_name}'\"")
|
run(['ssh', '-i', expanduser('~/.ssh/sshyp'), '-p', port, f"{username_ssh}@{ip}",
|
||||||
|
f'python3 -c \'from pathlib import Path; Path("{directory_ssh}{_new_name}")'
|
||||||
|
f'.mkdir(mode=0o700, parents=True, exist_ok=True)\''])
|
||||||
|
else:
|
||||||
|
move(f"{directory}{_entry_name}", f"{directory}{_new_name}")
|
||||||
|
else:
|
||||||
|
if ssh_error != 1:
|
||||||
|
copy(f"{directory}{_entry_name}.gpg", f"{directory}{_new_name}.gpg")
|
||||||
else:
|
else:
|
||||||
move(f"{directory}{_entry_name}.gpg", f"{directory}{_new_name}.gpg")
|
move(f"{directory}{_entry_name}.gpg", f"{directory}{_new_name}.gpg")
|
||||||
if ssh_error != 1:
|
if ssh_error != 1:
|
||||||
system(f"ssh -i '{path.expanduser('~/.ssh/sshyp')}' -p {port} {username_ssh}@{ip} \"cd /lib/sshyp; python -c "
|
run(['ssh', '-i', expanduser('~/.ssh/sshyp'), '-p', port, f"{username_ssh}@{ip}",
|
||||||
f"'import sshypRemote; sshypRemote.delete(\"'\"{_entry_name}\"'\", \"'\"remotely\"'\")'\"")
|
f'cd /lib/sshyp; python3 -c \'from sshync import delete; delete("{_entry_name}", "remotely")\''])
|
||||||
|
|
||||||
|
|
||||||
def edit(): # edits the contents of an entry
|
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")
|
print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) does not exist\u001b[0m\n")
|
||||||
s_exit(3)
|
s_exit(3)
|
||||||
_shm_folder, _shm_entry = shm_gen()
|
_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':
|
if argument_list[2] == 'username' or argument_list[2] == '-u':
|
||||||
_detail, _edit_line = str(input('username: ')), 1
|
_detail, _edit_line = str(input('username: ')), 1
|
||||||
elif argument_list[2] == 'password' or argument_list[2] == '-p':
|
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")
|
remove(f"{directory}{_entry_name}.gpg")
|
||||||
print('\n\u001b[1mentry preview:\u001b[0m')
|
print('\n\u001b[1mentry preview:\u001b[0m')
|
||||||
entry_reader(f"{tmp_dir}{_shm_folder}/{_shm_entry}")
|
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
|
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) ')
|
_add_note = input('add a note to this entry? (y/N) ')
|
||||||
_shm_folder, _shm_entry = shm_gen()
|
_shm_folder, _shm_entry = shm_gen()
|
||||||
if _add_note.lower() == 'y':
|
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()
|
_notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'r').read()
|
||||||
else:
|
else:
|
||||||
_notes = ''
|
_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))
|
.writelines(optimized_edit([_password, _username, _url, _notes], None, -1))
|
||||||
else:
|
else:
|
||||||
_shm_folder, _shm_entry = shm_gen()
|
_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)
|
_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)
|
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w').writelines(_new_lines)
|
||||||
remove(f"{directory}{_entry_name}.gpg")
|
remove(f"{directory}{_entry_name}.gpg")
|
||||||
print('\n\u001b[1mentry preview:\u001b[0m')
|
print('\n\u001b[1mentry preview:\u001b[0m')
|
||||||
entry_reader(f"{tmp_dir}{_shm_folder}/{_shm_entry}")
|
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
|
def copy_data(): # copies a specified field of an entry to the clipboard
|
||||||
|
from subprocess import Popen
|
||||||
if len(argument_list) < 4:
|
if len(argument_list) < 4:
|
||||||
_entry_name = entry_name_fetch('entry to copy: ')
|
_entry_name = entry_name_fetch('entry to copy: ')
|
||||||
else:
|
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")
|
print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) does not exist\u001b[0m\n")
|
||||||
s_exit(3)
|
s_exit(3)
|
||||||
_shm_folder, _shm_entry = shm_gen()
|
_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)
|
||||||
_copy_line = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'r').readlines()
|
_copy_line, _index = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'r').readlines(), 0
|
||||||
if uname()[0] == 'Haiku': # Haiku clipboard detection
|
|
||||||
if argument_list[2] == 'username' or argument_list[2] == '-u':
|
if argument_list[2] == 'username' or argument_list[2] == '-u':
|
||||||
system('clipboard -c ' + "'" + _copy_line[1].rstrip().replace("'", "'\\''") + "'")
|
_index = 1
|
||||||
elif argument_list[2] == 'password' or argument_list[2] == '-p':
|
elif argument_list[2] == 'password' or argument_list[2] == '-p':
|
||||||
system('clipboard -c ' + "'" + _copy_line[0].rstrip().replace("'", "'\\''") + "'")
|
_index = 0
|
||||||
elif argument_list[2] == 'url' or argument_list[2] == '-l':
|
elif argument_list[2] == 'url' or argument_list[2] == '-l':
|
||||||
system('clipboard -c ' + "'" + _copy_line[2].rstrip().replace("'", "'\\''") + "'")
|
_index = 2
|
||||||
elif argument_list[2] == 'note' or argument_list[2] == '-n':
|
elif argument_list[2] == 'note' or argument_list[2] == '-n':
|
||||||
system('clipboard -c ' + "'" + _copy_line[3].rstrip().replace("'", "'\\''") + "'")
|
_index = 3
|
||||||
Popen('sleep 30; clipboard -r', shell=True, close_fds=True)
|
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
|
elif Path("/data/data/com.termux").exists(): # Termux (Android) clipboard detection
|
||||||
if argument_list[2] == 'username' or argument_list[2] == '-u':
|
run(['termux-clipboard-set', _copy_line[_index].rstrip()])
|
||||||
system('termux-clipboard-set ' + "'" + _copy_line[1].rstrip().replace("'", "'\\''") + "'")
|
Popen("sleep 30; termux-clipboard-set ''", shell=True)
|
||||||
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)
|
|
||||||
else: # X11 clipboard detection
|
else: # X11 clipboard detection
|
||||||
if argument_list[2] == 'username' or argument_list[2] == '-u':
|
run(['xclip', '-sel', 'c'], stdin=Popen(['echo', '-n', _copy_line[_index].rstrip()], stdout=PIPE).stdout)
|
||||||
system('echo -n ' + "'" + _copy_line[1].rstrip().replace("'", "'\\''") + "'" + ' | xclip -sel c')
|
Popen("sleep 30; echo -n '' | xclip -sel c", shell=True)
|
||||||
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)
|
|
||||||
rmtree(f"{tmp_dir}{_shm_folder}")
|
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: ')
|
_entry_name = entry_name_fetch('entry/folder to shear: ')
|
||||||
else:
|
else:
|
||||||
_entry_name = entry_name_fetch(1)
|
_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:
|
if ssh_error != 1:
|
||||||
system(f"ssh -i '{path.expanduser('~/.ssh/sshyp')}' -p {port} {username_ssh}@{ip} \"cd /lib/sshyp; python -c "
|
run(['ssh', '-i', expanduser('~/.ssh/sshyp'), '-p', port, f"{username_ssh}@{ip}",
|
||||||
f"'import sshypRemote; sshypRemote.delete(\"'\"{_entry_name}\"'\", \"'\"remotely\"'\")'\"")
|
f'cd /lib/sshyp; python3 -c \'from sshync import delete; delete("{_entry_name}", "remotely")\''])
|
||||||
else:
|
else:
|
||||||
from sshypRemote import delete as offline_delete
|
offline_delete(_entry_name, 'locally')
|
||||||
offline_delete(_entry_name, '')
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@@ -845,31 +794,27 @@ if __name__ == "__main__":
|
|||||||
# retrieve typed argument
|
# retrieve typed argument
|
||||||
argument_list = argv
|
argument_list = argv
|
||||||
argument = ' '.join(argument_list[1:])
|
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
|
# import saved userdata
|
||||||
device_type = ''
|
device_type = ''
|
||||||
if argument != 'tweak':
|
if argument != 'tweak':
|
||||||
tmp_dir = path.expanduser('~/.config/sshyp/tmp/')
|
tmp_dir = expanduser('~/.config/sshyp/tmp/')
|
||||||
try:
|
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()
|
device_type = sshyp_data[0].rstrip()
|
||||||
if device_type == 'client':
|
if device_type == 'client':
|
||||||
directory = path.expanduser('~/.local/share/sshyp/')
|
directory = expanduser('~/.local/share/sshyp/')
|
||||||
gpg_id = sshyp_data[1].rstrip()
|
gpg_id = sshyp_data[1].rstrip()
|
||||||
editor = sshyp_data[2].rstrip()
|
editor = sshyp_data[2].rstrip()
|
||||||
quick_unlock_enabled = sshyp_data[3].rstrip()
|
quick_unlock_enabled = sshyp_data[3].rstrip()
|
||||||
if Path(path.expanduser('~/.config/sshyp/sshyp.sshync')).is_file():
|
if Path(expanduser('~/.config/sshyp/sshyp.sshync')).is_file():
|
||||||
ssh_info = sshync.get_profile(path.expanduser('~/.config/sshyp/sshyp.sshync'))
|
ssh_info = get_profile(expanduser('~/.config/sshyp/sshyp.sshync'))
|
||||||
username_ssh = ssh_info[0].rstrip()
|
username_ssh = ssh_info[0].rstrip()
|
||||||
ip = ssh_info[1].rstrip()
|
ip = ssh_info[1].rstrip()
|
||||||
port = ssh_info[2].rstrip()
|
port = ssh_info[2].rstrip()
|
||||||
directory_ssh = str(ssh_info[4].rstrip())
|
directory_ssh = str(ssh_info[4].rstrip())
|
||||||
client_device_id = listdir(path.expanduser('~/.config/sshyp/devices'))[0].rstrip()
|
client_device_id = listdir(expanduser('~/.config/sshyp/devices'))[0].rstrip()
|
||||||
ssh_error = int(open(path.expanduser('~/.config/sshyp/ssh-error')).read().rstrip())
|
ssh_error = int(open(expanduser('~/.config/sshyp/ssh-error')).read().rstrip())
|
||||||
if ssh_error != 0:
|
if ssh_error != 0:
|
||||||
ssh_error = copy_id_check(port, username_ssh, ip, client_device_id)
|
ssh_error = copy_id_check(port, username_ssh, ip, client_device_id)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -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
@@ -104,7 +104,6 @@ urls {
|
|||||||
cp -r lib/. output/haikutemp/lib/sshyp/
|
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/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/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
|
ln -s /system/lib/sshyp/sshyp.py output/haikutemp/bin/sshyp
|
||||||
cp -r share/doc/sshyp/. output/haikutemp/documentation/packages/sshyp/
|
cp -r share/doc/sshyp/. output/haikutemp/documentation/packages/sshyp/
|
||||||
cp -r share/licenses/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/bin/sshyp
|
||||||
/usr/lib/sshyp/sshyp.py
|
/usr/lib/sshyp/sshyp.py
|
||||||
/usr/lib/sshyp/sshync.py
|
/usr/lib/sshyp/sshync.py
|
||||||
/usr/lib/sshyp/sshypRemote.py
|
|
||||||
/usr/share/bash-completion/completions/sshyp
|
/usr/share/bash-completion/completions/sshyp
|
||||||
%license /usr/share/licenses/sshyp/license
|
%license /usr/share/licenses/sshyp/license
|
||||||
%changelog /usr/share/doc/sshyp/changelog
|
%doc
|
||||||
%doc /usr/share/man/man1/sshyp.1.gz
|
/usr/share/doc/sshyp/changelog
|
||||||
|
/usr/share/man/man1/sshyp.1.gz
|
||||||
" > ~/rpmbuild/SPECS/sshyp.spec
|
" > ~/rpmbuild/SPECS/sshyp.spec
|
||||||
rpmbuild -bb ~/rpmbuild/SPECS/sshyp.spec
|
rpmbuild -bb ~/rpmbuild/SPECS/sshyp.spec
|
||||||
mv ~/rpmbuild/RPMS/noarch/* output/
|
mv ~/rpmbuild/RPMS/noarch/* output/
|
||||||
@@ -239,7 +238,6 @@ prefix: /
|
|||||||
echo "/usr/bin/sshyp
|
echo "/usr/bin/sshyp
|
||||||
/usr/lib/sshyp/sshync.py
|
/usr/lib/sshyp/sshync.py
|
||||||
/usr/lib/sshyp/sshyp.py
|
/usr/lib/sshyp/sshyp.py
|
||||||
/usr/lib/sshyp/sshypRemote.py
|
|
||||||
/usr/share/bash-completion/completions/sshyp
|
/usr/share/bash-completion/completions/sshyp
|
||||||
/usr/share/doc/sshyp/changelog
|
/usr/share/doc/sshyp/changelog
|
||||||
/usr/share/licenses/sshyp/license
|
/usr/share/licenses/sshyp/license
|
||||||
|
|||||||
+32
-39
@@ -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
|
this release ties up many of sshyp's loose ends where there was
|
||||||
new features that reduce wasted time
|
room for major performance, compatibility, and security improvements
|
||||||
|
|
||||||
compatibility-breaking changes:
|
compatibility-breaking changes:
|
||||||
|
|
||||||
- new configuration options (quick-unlock, offline mode) have been added and
|
- due to a near full re-write of the syncing functionality, all clients and servers
|
||||||
the configuration files have been reorganized
|
must be updated to this release (v1.3.0 is not backwards compatible with any prior release)
|
||||||
^ simply running "sshyp tweak" and following the setup wizard will correct any compatibility
|
- it is recommended to either delete the contents of ~/.config/sshyp/deleted (on the server-side)
|
||||||
issues
|
or sync all of your clients before updating
|
||||||
- for quick-unlock security, device names have been replaced with more secure device ids
|
^ old entries in this folder will throw errors with v1.3.0
|
||||||
^ older device names are still compatible, but for security reasons it is recommended
|
|
||||||
to delete any pre-existing device names from the server and allow "sshyp tweak" to re-register
|
|
||||||
your devices
|
|
||||||
|
|
||||||
user-facing features:
|
user-facing features:
|
||||||
|
|
||||||
- quick-unlock mode has been added
|
- none - all changes were under-the-hood - the user experience should be
|
||||||
^ this allows you to use a shortened version of your password by verifying that your device
|
exactly the same as v1.2.0 - just faster, less buggy, and more secure
|
||||||
is whitelisted on your sshyp server - it's both faster and more secure than standard unlock,
|
|
||||||
but it requires an active connection to your sshyp server to authenticate (otherwise it will
|
|
||||||
fall back to standard unlock)
|
|
||||||
- full support for offline usage
|
|
||||||
^ though sshyp could be used without a server before, it now can be configured to not attempt
|
|
||||||
to find one ever - this saves time and hides sync failure error messages
|
|
||||||
- bash completions have been added
|
|
||||||
^ if you have bash-completion installed, you can now use the tab key in bash to auto-complete
|
|
||||||
sshyp arguments and entry names (client only, not added for server-specific arguments)
|
|
||||||
^ if you do not have bash-completion installed, you can source
|
|
||||||
/usr/share/bash-completion/completions/sshyp (Linux/BSD) or
|
|
||||||
/boot/system/data/bash-completion/completions/sshyp (Haiku) in your ~/.bashrc to
|
|
||||||
use this feature
|
|
||||||
|
|
||||||
fixes/optimizations:
|
major fixes/optimizations:
|
||||||
|
|
||||||
- fixed entries with multi-word titles failing to decrypt
|
- a near full re-write of the syncing functionality
|
||||||
- password generation is now much faster and more resource efficient
|
^ all syncing logic has been moved into sshync.py (from sshyp.py and sshypRemote.py)
|
||||||
- there is no longer a length limit on generated passwords
|
^ in my setup, a dry, local "sshyp sync" went from 2.00+ seconds (v1.2.0) to 0.36 seconds (v1.3.0)
|
||||||
- improved visual consistency of help menus
|
^ the performance improvements are even greater when syncing from outside your local network
|
||||||
- rarely used modules are now imported only when needed
|
- the following character sequences will no longer break the syncing logic: "@", "^&*", and "*&^"
|
||||||
- sshyp now uses one fewer configuration file
|
^ 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:
|
other notable changes:
|
||||||
|
|
||||||
- sshyp is now specifically licensed under the GPL-3.0-only (keyword: only)
|
- quick-unlock password input is now hidden while the user is typing
|
||||||
- sshyp now has some possible arguments and its own help menu when running in server mode
|
^ user input is now invisible to prevent snooping
|
||||||
- temporary files in /dev/shm are now generated with more complex names
|
- lots of smaller optimizations not listed here
|
||||||
- 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)
|
|
||||||
|
|
||||||
<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
|
<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user