mirror of
https://github.com/rwinkhart/sshyp.git
synced 2026-09-03 07:37:16 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f5a2c8f3c | ||
|
|
386044d081 | ||
|
|
c651c41a41 | ||
|
|
7cb245edde | ||
|
|
edf31f9a7c | ||
|
|
362b1aa025 | ||
|
|
5633520d82 | ||
|
|
a487bd561a | ||
|
|
cbc0538e82 | ||
|
|
5c589de4f2 | ||
|
|
44392467e5 | ||
|
|
18aacf648b | ||
|
|
ac621a8a73 | ||
|
|
98f76ac1ab | ||
|
|
bdff85f024 | ||
|
|
2bcabe8597 | ||
|
|
7259a8732c | ||
|
|
20cdc27b88 | ||
|
|
2f6bdb8d2e | ||
|
|
eb4fae3e9e | ||
|
|
30afbafcbf | ||
|
|
e010180219 | ||
|
|
1f4e7d5cae | ||
|
|
67983da22e | ||
|
|
aa0bd017c9 | ||
|
|
5a4e952d4d | ||
|
|
db05e9b05e | ||
|
|
397ad7d409 | ||
|
|
5af02866b4 | ||
|
|
e8267f838a | ||
|
|
e4fbcd6825 | ||
|
|
9ba4b8d724 | ||
|
|
0b73d4016f |
@@ -1,7 +1,7 @@
|
||||

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

|
||||

|
||||
[](https://github.com/rwinkhart/sshyp/releases)
|
||||
|
||||
[](https://github.com/rwinkhart/sshyp/actions/workflows/codeql-analysis.yml)
|
||||
@@ -77,9 +77,10 @@ All available options can be found with `sshyp help`, or alternatively, in the m
|
||||
# Roadmap
|
||||
Short-term Goals:
|
||||
|
||||
- migrate from gpg to a better-suited utility focused on symmetric cipher encryption
|
||||
- implement a method for tracking how long it has been since a password has been changed (password aging)
|
||||
- find and fix more bugs
|
||||
|
||||
Long-term Goals:
|
||||
|
||||
- improve OS compatibility
|
||||
- migrate from gpg to a better-suited utility focused on symmetric cipher encryption
|
||||
- seize the thrones, shear the humans
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
git add -f extra lib/sshyp.py lib/sshync.py lib/stweak.py port-jobs share LICENSE README.md package.sh commit.sh .gitignore
|
||||
git add -f extra lib/sshyp.py lib/sshync.py lib/stweak.py lib/clipclear.py port-jobs share LICENSE README.md package.sh commit.sh .gitignore
|
||||
git commit -m "$1"
|
||||
git push
|
||||
|
||||
@@ -1,3 +1,34 @@
|
||||
sshyp v1.5.1
|
||||
10/24/2023
|
||||
|
||||
the fortified flock update - patch one
|
||||
|
||||
this release fixes both long and short-standing bugs that managed to escape discovery in testing;
|
||||
it also addresses some issues that first-time users may be put-off by upon encountering
|
||||
|
||||
user-facing features:
|
||||
|
||||
- the clipboard is now only cleared if the contents of the clipboard still match what was placed onto it by sshyp
|
||||
^ this means that sshyp will no longer remove data from the clipboard that it did not place there
|
||||
- a warning/explanation is now provided when the user attempts to sync in offline mode
|
||||
- a readable error is now provided if the user attempts to edit a note and the specified text editor cannot be found
|
||||
- the optional configuration menu is now explained when encountered during "sshyp init"
|
||||
- the man page now includes common troubleshooting information
|
||||
|
||||
fixes/optimizations:
|
||||
|
||||
- custom, user-provided ssh keyfiles are now properly utilized in all cases
|
||||
^ previously, using custom ssh keyfiles would break various features
|
||||
- clipboard contents are no longer leaked via the process list on Wayland
|
||||
- interpolation has been disabled for ConfigParser, allowing "%" to be stored in configuration files
|
||||
- gpg keys with ":" or "\" in their names are now properly written to the configuration file
|
||||
- copy_id_check() and whitelist_verify() are now safely callable from extensions
|
||||
- the ssh key selection will no longer see "known_hosts.old" as a valid keyfile
|
||||
- when possible, data from the configuration file is now read as boolean values
|
||||
- the Haiku package has been updated to use Python 3.11
|
||||
|
||||
<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
|
||||
|
||||
sshyp v1.5.0
|
||||
07/12/2023
|
||||
|
||||
|
||||
+27
-1
@@ -1,4 +1,4 @@
|
||||
.TH sshyp 1 "12 July 2023" "v1.5.0" "sshyp man page"
|
||||
.TH sshyp 1 "24 October 2023" "v1.5.1" "sshyp man page"
|
||||
.SH NAME
|
||||
\fBsshyp\fR - Simple, self-hosted, synchronized password management for UNIX(-like) systems. Alternative to (and compatible with) pass/password-store.
|
||||
.SH SYNOPSIS
|
||||
@@ -99,6 +99,32 @@ Client setup:
|
||||
Done!
|
||||
|
||||
Please note that the server setup intentionally does not allow the reading of entries (it does not allow adding a gpg decryption key). For security purposes, only clients can read entries.
|
||||
.SH TROUBLESHOOTING
|
||||
.B Clipboard not clearing:
|
||||
|
||||
The intended way for sshyp to interact with the system clipboard is for it to clear it 30 seconds after copying a field. Unfortunately, this does not work by default on all systems due to the prevalence of clipboard managers.
|
||||
|
||||
Clipboard managers save a history of what has been copied to the clipboard, which is already a big enough issue on its own for people who copy sensitive information to their clipboard. Some clipboard managers simply will not allow the clipboard to be empty and will replace its contents with the last copied item if you attempt to clear it. One such naughty clipboard manager is KDE Klipper, which comes packaged into KDE Plasma and is typically enabled by default on most distributions. Due to this behavior, KDE Klipper breaks sshyp's clipboard clearing functionality and should not be left enabled.
|
||||
|
||||
It is likely other popular clipboard managers exhibit this behavior as well (Klipper is just the one I first noticed it with). Clipboard managers should not be enabled by default in any environment or distribution due to their potential security implications.
|
||||
|
||||
.B Shell completions not working:
|
||||
ZSH:
|
||||
Make sure your ~/.zshrc contains the following:
|
||||
autoload -Uz compinit && compinit
|
||||
Restart your shell
|
||||
|
||||
Bash:
|
||||
Install your distribution's 'bash-completion' package and restart your shell
|
||||
OR
|
||||
Source the completion script manually by adding the following to your ~/.bashrc (then restart your shell):
|
||||
Linux:
|
||||
source /usr/share/bash-completion/completions/sshyp
|
||||
FreeBSD:
|
||||
source /usr/local/share/bash-completion/completions/sshyp
|
||||
Haiku:
|
||||
source /system/data/bash-completion/completions/sshyp
|
||||
|
||||
.SH EXIT CODES
|
||||
0 - no error
|
||||
|
||||
|
||||
Executable
+42
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
from hashlib import sha512
|
||||
from subprocess import PIPE, run
|
||||
from sys import argv
|
||||
from time import sleep
|
||||
|
||||
# set period of time to wait before attempting to clear clipboard
|
||||
sleep(30)
|
||||
# enable the storing of clipboard contents hash for later comparison
|
||||
hash_paste = sha512()
|
||||
|
||||
# PORT START CLIPCLEAR
|
||||
if argv[2] == 'wsl':
|
||||
hash_paste.update(run(('powershell.exe', '-c', 'Get-Clipboard'), stdout=PIPE).stdout.strip())
|
||||
if argv[1] == hash_paste.hexdigest():
|
||||
run(('powershell.exe', '-c', 'Set-Clipboard'))
|
||||
|
||||
elif argv[2] == 'wayland':
|
||||
hash_paste.update(run('wl-paste', stdout=PIPE).stdout.strip())
|
||||
if argv[1] == hash_paste.hexdigest():
|
||||
run(('wl-copy', '-c'))
|
||||
|
||||
elif argv[2] == 'haiku':
|
||||
hash_paste.update(run(('clipboard', '-p'), stdout=PIPE).stdout.strip())
|
||||
if argv[1] == hash_paste.hexdigest():
|
||||
run(('clipboard', '-r'))
|
||||
|
||||
elif argv[2] == 'mac':
|
||||
hash_paste.update(run('pbpaste', stdout=PIPE).stdout.strip())
|
||||
if argv[1] == hash_paste.hexdigest():
|
||||
run('pbcopy', input=b'')
|
||||
|
||||
elif argv[2] == 'termux':
|
||||
hash_paste.update(run('termux-clipboard-get', stdout=PIPE).stdout.strip())
|
||||
if argv[1] == hash_paste.hexdigest():
|
||||
run(("termux-clipboard-set", "''"))
|
||||
|
||||
elif argv[2] == 'x11':
|
||||
hash_paste.update(run(('xclip', '-o', '-sel', 'c'), stdout=PIPE).stdout.strip())
|
||||
if argv[1] == hash_paste.hexdigest():
|
||||
run(('xclip', '-i', '/dev/null', '-sel', 'c'))
|
||||
# PORT END CLIPCLEAR
|
||||
+1
-1
@@ -124,7 +124,7 @@ def sort_titles_mods(_list_1, _list_2):
|
||||
# returns a list of data read from a sshync job profile
|
||||
def get_profile(_profile_dir):
|
||||
from configparser import ConfigParser
|
||||
_profile_data = ConfigParser()
|
||||
_profile_data = ConfigParser(interpolation=None)
|
||||
if isfile(_profile_dir):
|
||||
_profile_data.read(_profile_dir)
|
||||
else:
|
||||
|
||||
+51
-37
@@ -123,7 +123,11 @@ def edit_note(_note_lines, _exit_on_match=False):
|
||||
with NamedTemporaryFile(mode='w+') as _tmp:
|
||||
_tmp.write(_joined_note_lines)
|
||||
_tmp.seek(0)
|
||||
run((editor, _tmp.name))
|
||||
try:
|
||||
run((editor, _tmp.name))
|
||||
except FileNotFoundError:
|
||||
print(f"\n\u001b[38;5;9merror: the configured text editor ({editor}) cannot be found on this system\n\n"
|
||||
f"please either install the editor or re-configure the active editor using 'sshyp tweak'\u001b[0m\n")
|
||||
_tmp.seek(0)
|
||||
_new_note = _tmp.read().rstrip()
|
||||
if _exit_on_match and _joined_note_lines == _new_note:
|
||||
@@ -143,8 +147,8 @@ def decrypt(_entry_dir, _quick_verify=None, _quick_pass=None):
|
||||
_contents = None
|
||||
|
||||
# check quick-unlock status, fetch passphrase
|
||||
if _quick_verify == 'true':
|
||||
_quick_pass = whitelist_verify(port, username_ssh, ip, client_device_id)
|
||||
if _quick_verify:
|
||||
_quick_pass = whitelist_verify(port, username_ssh, ip, client_device_id, identity)
|
||||
else:
|
||||
if _quick_pass is None:
|
||||
_quick_pass = False
|
||||
@@ -180,14 +184,14 @@ def decrypt(_entry_dir, _quick_verify=None, _quick_pass=None):
|
||||
|
||||
|
||||
# checks the user's whitelist status and fetches the full gpg key password if possible
|
||||
def whitelist_verify(_port, _username_ssh, _ip, _client_device_id):
|
||||
def whitelist_verify(_port, _username_ssh, _ip, _client_device_id, _identity):
|
||||
try:
|
||||
run(('gpg', '--pinentry-mode', 'cancel', '-qd', '--output', '/dev/null',
|
||||
f"{home}/.config/sshyp/lock.gpg"), stderr=DEVNULL, check=True)
|
||||
return False
|
||||
except CalledProcessError:
|
||||
_i, _full_password = 0, ''
|
||||
_server_whitelist = run(('ssh', '-i', f"{home}/.ssh/sshyp", '-p', _port, f"{_username_ssh}@{_ip}",
|
||||
_server_whitelist = run(('ssh', '-i', _identity, '-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:
|
||||
@@ -195,7 +199,7 @@ def whitelist_verify(_port, _username_ssh, _ip, _client_device_id):
|
||||
from getpass import getpass
|
||||
_quick_unlock_password = getpass(prompt='\nquick-unlock pin: ')
|
||||
_quick_unlock_password_excluded = \
|
||||
run(('ssh', '-i', f"{home}/.ssh/sshyp", '-p', _port, f"{_username_ssh}@{_ip}",
|
||||
run(('ssh', '-i', _identity, '-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):
|
||||
@@ -256,22 +260,22 @@ def line_edit(_lines, _edit_data, _edit_line):
|
||||
|
||||
|
||||
# attempts to connect to the user's server via ssh to register the device for syncing
|
||||
def copy_id_check(_port, _username_ssh, _ip, _client_device_id, _sshyp_data):
|
||||
def copy_id_check(_port, _username_ssh, _ip, _client_device_id, _identity, _sshyp_data):
|
||||
from stweak import write_config
|
||||
if not _sshyp_data.has_section('CLIENT-ONLINE'):
|
||||
_sshyp_data.add_section('CLIENT-ONLINE')
|
||||
try:
|
||||
run(('ssh', '-o', 'ConnectTimeout=3', '-i', f"{home}/.ssh/sshyp", '-p', _port, f"{_username_ssh}@{_ip}",
|
||||
run(('ssh', '-o', 'ConnectTimeout=3', '-i', _identity, '-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 '
|
||||
print(f'\n\u001b[38;5;9mwarning: ssh connection could not be made - ensure the public key ({_identity}) 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')
|
||||
_sshyp_data.set('CLIENT-ONLINE', 'ssh_error', '1')
|
||||
_sshyp_data.set('CLIENT-ONLINE', 'ssh_error', 'true')
|
||||
write_config(_sshyp_data)
|
||||
return True
|
||||
_sshyp_data.set('CLIENT-ONLINE', 'ssh_error', '0')
|
||||
_sshyp_data.set('CLIENT-ONLINE', 'ssh_error', 'false')
|
||||
write_config(_sshyp_data)
|
||||
return False
|
||||
|
||||
@@ -290,7 +294,7 @@ def print_info():
|
||||
}}-}}-*]{4*' '}`..'..'{9*' '}\u001b[0m♥♥♥\u001b[0m{9*' '}`..'..'{6*' '}|
|
||||
{4*' '}◎-◎{4*' '}//{3*' '}\\\\{10*' '}\u001b[38;5;9m♥\u001b[0m{10*' '}//{3*' '}\\\\{5*' '}/|\\""")
|
||||
print(f"{_border}{_blank}\n\u001b[38;5;7;48;5;8m\\\u001b[38;5;15;48;5;15m{18*' '}\u001b[38;5;15;48;5;8msshyp "
|
||||
f"version 1.5.0\u001b[38;5;15;48;5;15m{18*' '}\u001b[38;5;7;48;5;8m/\u001b[0m")
|
||||
f"version 1.5.1\u001b[38;5;15;48;5;15m{18*' '}\u001b[38;5;7;48;5;8m/\u001b[0m")
|
||||
print(f"\u001b[38;5;7;48;5;8m\\\u001b[38;5;15;48;5;15m{14*' '}\u001b[38;5;15;48;5;8mthe fortified flock"
|
||||
f" update\u001b[38;5;15;48;5;15m{15*' '}\u001b[38;5;7;48;5;8m/\u001b[0m\n{_blank}")
|
||||
print(f"\u001b[38;5;7;48;5;8m\\\u001b[38;5;15;48;5;15m{9*' '}\u001b[38;5;15;48;5;8mcopyright 2021-2023 ",
|
||||
@@ -424,7 +428,7 @@ def add_entry():
|
||||
def add_folder():
|
||||
Path(directory + entry_name).mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
if not ssh_error:
|
||||
run(('ssh', '-i', f"{home}/.ssh/sshyp", '-p', port, f"{username_ssh}@{ip}",
|
||||
run(('ssh', '-i', identity, '-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)\''))
|
||||
|
||||
@@ -453,13 +457,13 @@ def rename():
|
||||
# if renaming a folder
|
||||
if not ssh_error:
|
||||
Path(f"{directory}{_new_name}").mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
run(('ssh', '-i', f"{home}/.ssh/sshyp", '-p', port, f"{username_ssh}@{ip}",
|
||||
run(('ssh', '-i', identity, '-p', port, f"{username_ssh}@{ip}",
|
||||
f'python3 -c \'from pathlib import Path; Path("{directory_ssh}{entry_name}")'
|
||||
f'.rename(Path("{directory_ssh}{_new_name}"))\''))
|
||||
else:
|
||||
move(f"{directory}{entry_name}", f"{directory}{_new_name}")
|
||||
if not ssh_error:
|
||||
run(('ssh', '-i', f"{home}/.ssh/sshyp", '-p', port, f"{username_ssh}@{ip}",
|
||||
run(('ssh', '-i', identity, '-p', port, f"{username_ssh}@{ip}",
|
||||
f'cd /usr/lib/sshyp; python3 -c \'from sshync import delete; delete("{entry_name}", "remotely", True)\''))
|
||||
|
||||
|
||||
@@ -517,6 +521,7 @@ def gen():
|
||||
|
||||
# copies a specified field of an entry to the clipboard
|
||||
def copy_data():
|
||||
from hashlib import sha512
|
||||
from subprocess import Popen
|
||||
# ensure the copy target is an entry
|
||||
target_type_check(entry_name, True, True)
|
||||
@@ -533,33 +538,38 @@ def copy_data():
|
||||
# ensure field is not blank
|
||||
if _copy_subject == '':
|
||||
raise IndexError
|
||||
|
||||
# store hashed _copy_subject for later comparison
|
||||
_hash = sha512()
|
||||
_hash.update(_copy_subject.encode('utf-8'))
|
||||
|
||||
# PORT START CLIPBOARD
|
||||
# WSL clipboard detection
|
||||
if 'WSL_DISTRO_NAME' in environ:
|
||||
run(('powershell.exe', '-c', "Set-Clipboard '" + _copy_subject.replace("'", "''") + "'"))
|
||||
Popen("sleep 30; powershell.exe -c Set-Clipboard ''", shell=True, stdout=DEVNULL, stderr=DEVNULL)
|
||||
Popen((realpath(__file__).rsplit('/', 1)[0] + "/clipclear.py", _hash.hexdigest(), 'wsl'), stdout=DEVNULL,
|
||||
stderr=DEVNULL)
|
||||
# Wayland clipboard detection
|
||||
elif 'WAYLAND_DISPLAY' in environ:
|
||||
run(('wl-copy', _copy_subject))
|
||||
Popen('sleep 30; wl-copy -c', shell=True)
|
||||
run('wl-copy', stdin=Popen(('printf', '%b', _copy_subject.replace('\\', '\\\\')), stdout=PIPE).stdout)
|
||||
Popen((realpath(__file__).rsplit('/', 1)[0] + "/clipclear.py", _hash.hexdigest(), 'wayland'))
|
||||
# Haiku clipboard detection
|
||||
elif uname()[0] == 'Haiku':
|
||||
run(('clipboard', '-c', _copy_subject))
|
||||
Popen('sleep 30; clipboard -r', shell=True)
|
||||
Popen((realpath(__file__).rsplit('/', 1)[0] + "/clipclear.py", _hash.hexdigest(), 'haiku'))
|
||||
# MacOS clipboard detection
|
||||
elif uname()[0] == 'Darwin':
|
||||
run('pbcopy', stdin=Popen(('printf', _copy_subject.replace('\\', '\\\\').replace('%', '%%')), stdout=PIPE)
|
||||
.stdout)
|
||||
Popen("sleep 30; printf '' | pbcopy", shell=True)
|
||||
run('pbcopy', stdin=Popen(('printf', '%b', _copy_subject.replace('\\', '\\\\')), stdout=PIPE).stdout)
|
||||
Popen((realpath(__file__).rsplit('/', 1)[0] + "/clipclear.py", _hash.hexdigest(), 'mac'))
|
||||
# Termux (Android) clipboard detection
|
||||
elif isdir("/data/data/com.termux"):
|
||||
run(('termux-clipboard-set', _copy_subject))
|
||||
Popen("sleep 30; termux-clipboard-set ''", shell=True)
|
||||
Popen((realpath(__file__).rsplit('/', 1)[0] + "/clipclear.py", _hash.hexdigest(), 'termux'))
|
||||
# X11 clipboard detection
|
||||
elif 'DISPLAY' in environ:
|
||||
run(('xclip', '-sel', 'c'), stdin=Popen(('printf', _copy_subject.replace('\\', '\\\\')
|
||||
.replace('%', '%%')), stdout=PIPE).stdout)
|
||||
Popen("sleep 30; printf '' | xclip -sel c", shell=True)
|
||||
run(('xclip', '-sel', 'c'), stdin=Popen(('printf', '%b', _copy_subject.replace('\\', '\\\\')), stdout=PIPE)
|
||||
.stdout)
|
||||
Popen((realpath(__file__).rsplit('/', 1)[0] + "/clipclear.py", _hash.hexdigest(), 'x11'))
|
||||
else:
|
||||
print('\n\u001b[38;5;9merror: clipboard tool could not be determined\n\nnote that the clipboard does not '
|
||||
'function in a raw tty\u001b[0m\n')
|
||||
@@ -570,7 +580,7 @@ def copy_data():
|
||||
def remove_data():
|
||||
decrypt(None, _quick_verify=quick_unlock_enabled)
|
||||
if not ssh_error:
|
||||
run(('ssh', '-i', f"{home}/.ssh/sshyp", '-p', port, f"{username_ssh}@{ip}",
|
||||
run(('ssh', '-i', identity, '-p', port, f"{username_ssh}@{ip}",
|
||||
f'cd /usr/lib/sshyp; python3 -c \'from sshync import delete; delete("{entry_name}", "remotely", True)\''))
|
||||
else:
|
||||
offline_delete(entry_name, 'locally', silent_sync)
|
||||
@@ -581,7 +591,7 @@ def extension_runner():
|
||||
_output_com, _extension_dir = None, realpath(__file__).rsplit('/', 1)[0] + '/extensions/'
|
||||
if isdir(_extension_dir):
|
||||
for _extension in listdir(_extension_dir):
|
||||
_extension_config = ConfigParser()
|
||||
_extension_config = ConfigParser(interpolation=None)
|
||||
_extension_config.read(_extension_dir + _extension)
|
||||
_input_com = _extension_config.get('config', 'input').split()
|
||||
if _input_com == arguments[arg_start:]:
|
||||
@@ -603,7 +613,7 @@ if __name__ == "__main__":
|
||||
ssh_error, success_flag, sync_flag, silent_sync, pass_show = False, False, False, False, False
|
||||
|
||||
# set to avoid PEP8 warnings
|
||||
arg_start, device_type = None, None
|
||||
arg_start, device_type, offline_mode_enabled = None, None, None
|
||||
|
||||
# retrieve typed argument
|
||||
arguments = argv[1:]
|
||||
@@ -624,27 +634,28 @@ if __name__ == "__main__":
|
||||
|
||||
# import saved userdata
|
||||
try:
|
||||
sshyp_data = ConfigParser()
|
||||
sshyp_data = ConfigParser(interpolation=None)
|
||||
sshyp_data.read(f"{home}/.config/sshyp/sshyp.ini")
|
||||
device_type = sshyp_data.get('GENERAL', 'device_type')
|
||||
if device_type == 'client':
|
||||
directory = f"{home}/.local/share/sshyp/"
|
||||
gpg_id = sshyp_data.get('CLIENT-GENERAL', 'gpg_id')
|
||||
editor = sshyp_data.get('CLIENT-GENERAL', 'text_editor')
|
||||
offline_mode_enabled = sshyp_data.get('CLIENT-GENERAL', 'offline_mode_enabled')
|
||||
if offline_mode_enabled == 'true':
|
||||
offline_mode_enabled = sshyp_data.getboolean('CLIENT-GENERAL', 'offline_mode_enabled')
|
||||
if offline_mode_enabled:
|
||||
ssh_error = True
|
||||
quick_unlock_enabled = 'false'
|
||||
quick_unlock_enabled = False
|
||||
else:
|
||||
quick_unlock_enabled = sshyp_data.get('CLIENT-ONLINE', 'quick_unlock_enabled')
|
||||
quick_unlock_enabled = sshyp_data.getboolean('CLIENT-ONLINE', 'quick_unlock_enabled')
|
||||
username_ssh = sshyp_data.get('SSHYNC', 'user')
|
||||
ip = sshyp_data.get('SSHYNC', 'ip')
|
||||
port = sshyp_data.get('SSHYNC', 'port')
|
||||
directory_ssh = sshyp_data.get('SSHYNC', 'remote_dir')
|
||||
identity = sshyp_data.get('SSHYNC', 'identity_file')
|
||||
client_device_id = listdir(f"{home}/.config/sshyp/devices")[0]
|
||||
ssh_error = int(sshyp_data.get('CLIENT-ONLINE', 'ssh_error'))
|
||||
if ssh_error == 1:
|
||||
ssh_error = copy_id_check(port, username_ssh, ip, client_device_id, sshyp_data)
|
||||
ssh_error = sshyp_data.getboolean('CLIENT-ONLINE', 'ssh_error')
|
||||
if ssh_error:
|
||||
ssh_error = copy_id_check(port, username_ssh, ip, client_device_id, identity, sshyp_data)
|
||||
except (FileNotFoundError, NoSectionError, NoOptionError):
|
||||
print(f"\n{73*'!'}")
|
||||
print("not all necessary configurations have been made - please run 'sshyp init'")
|
||||
@@ -714,6 +725,9 @@ if __name__ == "__main__":
|
||||
sync()
|
||||
elif (arg_count > 0 and arguments[0] == 'sync') or (arg_count > 1 and arguments[1] == 'shear'):
|
||||
sync('\n')
|
||||
elif arg_count > 0 and arguments[0] == 'sync' and offline_mode_enabled:
|
||||
print("\n\u001b[38;5;9mwarning: sshyp is currently configured in offline mode - ssh synchronization is "
|
||||
"disabled\u001b[0m\n")
|
||||
|
||||
# PORT START ARGS-SERVER
|
||||
# server arguments
|
||||
|
||||
+17
-15
@@ -10,7 +10,7 @@ from subprocess import PIPE, run
|
||||
# PORT START UNAME-IMPORT-STWEAK
|
||||
from os import uname
|
||||
# PORT END UNAME-IMPORT-STWEAK
|
||||
home, sshyp_data, stdscr = expanduser('~'), ConfigParser(), None
|
||||
home, sshyp_data, stdscr = expanduser('~'), ConfigParser(interpolation=None), None
|
||||
if isfile(f"{home}/.config/sshyp/sshyp.ini"):
|
||||
_exists_flag = True
|
||||
sshyp_data.read(f"{home}/.config/sshyp/sshyp.ini")
|
||||
@@ -99,7 +99,7 @@ def gpg_config():
|
||||
stdout=PIPE, text=True).stdout.splitlines() if _item.startswith('uid')]
|
||||
_named_uid_list = []
|
||||
for _uid in _uid_list:
|
||||
_named_uid_list.append(_uid.split(':')[9])
|
||||
_named_uid_list.append(_uid.split(':')[9].replace('\\x3a', ':').replace('\\x5c', '\\'))
|
||||
_named_uid_list.append('auto-generate')
|
||||
_gpg_id_sel = curses_radio(_named_uid_list, 'gpg key selection')
|
||||
if _gpg_id_sel == len(_named_uid_list)-1:
|
||||
@@ -151,7 +151,7 @@ def ssh_config():
|
||||
# ensure ~/.ssh directory exists
|
||||
Path(f"{home}/.ssh").mkdir(mode=0o700, exist_ok=True)
|
||||
for _file in listdir(f"{home}/.ssh"):
|
||||
if not _file.startswith('.') and _file not in ('known_hosts', 'authorized_keys') \
|
||||
if not _file.startswith('.') and _file not in ('known_hosts', 'known_hosts.old', 'authorized_keys') \
|
||||
and not _file.endswith('.pub') and isfile(f"{home}/.ssh/{_file}"):
|
||||
_keys.append(f"{home}/.ssh/{_file}")
|
||||
_keys.extend(['auto-generate', 'other (type the location)'])
|
||||
@@ -184,11 +184,11 @@ def ssh_config():
|
||||
sshyp_data.set('SSHYNC', 'remote_dir', f"/home/{_username_ssh}/.local/share/sshyp/")
|
||||
sshyp_data.set('SSHYNC', 'identity_file', _ssh_key)
|
||||
write_config()
|
||||
return _iport[1], _username_ssh, _iport[0]
|
||||
return _iport[1], _username_ssh, _iport[0], _ssh_key
|
||||
|
||||
|
||||
# device id configuration
|
||||
def dev_id_config(_ip, _username_ssh, _port):
|
||||
def dev_id_config(_ip, _username_ssh, _port, _identity):
|
||||
from sshyp import copy_id_check, string_gen
|
||||
_device_id_prefix = curses_text('name this device:\n\n\n\n\n(ctrl+g/enter to confirm)\n\nimportant: this '
|
||||
'id must be unique amongst your client devices\n\nthis is used to keep track of '
|
||||
@@ -200,7 +200,7 @@ def dev_id_config(_ip, _username_ssh, _port):
|
||||
remove(f"{home}/.config/sshyp/devices/{_id}")
|
||||
open(f"{home}/.config/sshyp/devices/{_device_id}", 'w')
|
||||
# test server connection and attempt to register device id
|
||||
copy_id_check(_ip, _username_ssh, _port, _device_id, sshyp_data)
|
||||
copy_id_check(_ip, _username_ssh, _port, _device_id, _identity, sshyp_data)
|
||||
|
||||
|
||||
# quick-unlock configuration
|
||||
@@ -360,8 +360,8 @@ def extension_downloader():
|
||||
from os import chmod
|
||||
from tempfile import gettempdir
|
||||
from urllib.request import urlopen, urlretrieve
|
||||
_file_data = urlopen("https://raw.githubusercontent.com/rwinkhart/sshyp-labs/main/pointers/v1.5.0").read()
|
||||
_pointer = ConfigParser()
|
||||
_file_data = urlopen("https://raw.githubusercontent.com/rwinkhart/sshyp-labs/main/pointers/v1.5.1").read()
|
||||
_pointer = ConfigParser(interpolation=None)
|
||||
_pointer.read_string(_file_data.decode('utf-8'))
|
||||
_extensions = _pointer.sections()
|
||||
_extensions.append('CANCEL')
|
||||
@@ -464,15 +464,15 @@ def global_menu(_scr, _device_type, _top_message):
|
||||
if not sshyp_data.has_option('CLIENT-ONLINE', 'quick_unlock_enabled'):
|
||||
quick_unlock_config(True)
|
||||
# set to None to check if modified later
|
||||
_ip, _username_ssh, _port = None, None, None
|
||||
_ip, _username_ssh, _port, _identity = None, None, None, None
|
||||
# ...and there is no sshync config present
|
||||
if not sshyp_data.has_section('SSHYNC'):
|
||||
_ip, _username_ssh, _port = ssh_config()
|
||||
_ip, _username_ssh, _port, _identity = ssh_config()
|
||||
# ...and there is no device ID present
|
||||
if not listdir(f"{home}/.config/sshyp/devices"):
|
||||
if None in (_ip, _username_ssh, _port):
|
||||
_ip, _username_ssh, _port = ssh_config()
|
||||
dev_id_config(_ip, _username_ssh, _port)
|
||||
_ip, _username_ssh, _port, _identity = ssh_config()
|
||||
dev_id_config(_ip, _username_ssh, _port, _identity)
|
||||
# ...or ssh_error is missing
|
||||
elif not sshyp_data.has_option('CLIENT-ONLINE', 'ssh_error'):
|
||||
sshyp_data.set('CLIENT-ONLINE', 'ssh_error', '1')
|
||||
@@ -492,7 +492,7 @@ def global_menu(_scr, _device_type, _top_message):
|
||||
if not sshyp_data.has_section('SSHYNC'):
|
||||
ssh_config()
|
||||
dev_id_config(sshyp_data.get('SSHYNC', 'ip'), sshyp_data.get('SSHYNC', 'user'),
|
||||
sshyp_data.get('SSHYNC', 'port'))
|
||||
sshyp_data.get('SSHYNC', 'port'), sshyp_data.get('SSHYNC', 'identity_file'))
|
||||
elif _choice == 4:
|
||||
editor_config(False)
|
||||
elif _choice == 5:
|
||||
@@ -555,10 +555,10 @@ def initial_setup(_scr):
|
||||
if _dev_sync_types[1] != 'true':
|
||||
|
||||
# ssh+sshync configuration
|
||||
_ip, _username_ssh, _port = ssh_config()
|
||||
_ip, _username_ssh, _port, _identity = ssh_config()
|
||||
|
||||
# device id configuration
|
||||
dev_id_config(_ip, _username_ssh, _port)
|
||||
dev_id_config(_ip, _username_ssh, _port, _identity)
|
||||
|
||||
# PORT START CLIPTOOL
|
||||
# check for clipboard tool and display warning if missing
|
||||
@@ -576,6 +576,8 @@ def initial_setup(_scr):
|
||||
# PORT END CLIPTOOL
|
||||
|
||||
# run optional configuration menu
|
||||
curses_radio(['okay'], 'required configuration complete\n\na menu for additional (optional) configuration will be '
|
||||
'displayed\n\nthis menu can be safely exited at any time')
|
||||
wrapped_entry(_dev_sync_types[0], 'additional configuration options:')
|
||||
|
||||
|
||||
|
||||
+11
-5
@@ -144,7 +144,7 @@ provides {
|
||||
requires {
|
||||
gnupg
|
||||
openssh
|
||||
python310
|
||||
python3.11
|
||||
}
|
||||
urls {
|
||||
\"https://github.com/rwinkhart/sshyp\"
|
||||
@@ -220,11 +220,17 @@ Section: utils
|
||||
Architecture: all
|
||||
Maintainer: Randall Winkhart <idgr at tutanota dot com>
|
||||
Description: A light-weight, self-hosted, synchronized password manager
|
||||
Depends: python3, gnupg, openssh-client
|
||||
Suggests: wl-clipboard, xclip, bash-completion
|
||||
Priority: optional
|
||||
Installed-Size: $size
|
||||
Depends: python3, gnupg, openssh-client
|
||||
" > output/debiantemp/sshyp_"$version"-"$revision"_all/DEBIAN/control
|
||||
if [ "$1" = 'Debian' ]; then
|
||||
printf "Suggests: wl-clipboard, xclip, bash-completion, openssh-server
|
||||
" >> output/debiantemp/sshyp_"$version"-"$revision"_all/DEBIAN/control
|
||||
else
|
||||
printf "Suggests: bash-completion, openssh-server
|
||||
" >> output/debiantemp/sshyp_"$version"-"$revision"_all/DEBIAN/control
|
||||
fi
|
||||
dpkg-deb --build --root-owner-group -z6 -Sextreme -Zxz output/debiantemp/sshyp_"$version"-"$revision"_all/
|
||||
mv output/debiantemp/sshyp_"$version"-"$revision"_all.deb output/"$special"-sshyp_"$version"-"$revision"_all.deb
|
||||
rm -rf output/debiantemp
|
||||
@@ -296,7 +302,7 @@ License: GPL-3.0-only
|
||||
URL: https://github.com/rwinkhart/sshyp
|
||||
Source0: GENERIC-FEDORA-sshyp-"$version".tar.xz
|
||||
Requires: python gnupg openssh-clients
|
||||
Recommends: wl-clipboard xclip bash-completion
|
||||
Recommends: wl-clipboard xclip bash-completion openssh-server
|
||||
%%description
|
||||
sshyp is a password-store compatible CLI password manager available for UNIX(-like) systems - its primary goal is to make syncing passwords and notes across devices as easy as possible via CLI.
|
||||
%%install
|
||||
@@ -381,7 +387,7 @@ printf "/usr/bin/sshyp
|
||||
cp lib/* port-jobs/working/
|
||||
cd port-jobs
|
||||
./CLIPTOOL.py LINUX
|
||||
./CLIPBOARD.py LINUX
|
||||
./CLIPBOARD.py BSD
|
||||
./UNAME.py TMP
|
||||
./COMMENTS.py ALL
|
||||
./BLANKS.py
|
||||
|
||||
+45
-29
@@ -3,44 +3,60 @@ import re
|
||||
from sys import argv, exit as s_exit
|
||||
|
||||
# read arguments
|
||||
arguments, replacement = argv[1:], None
|
||||
arguments, clip_replacement, clear_replacement = argv[1:], None, None
|
||||
|
||||
# define replacement text depending on arguments
|
||||
if len(arguments) > 0:
|
||||
if arguments[0] == 'WSL':
|
||||
replacement = """run(('powershell.exe', '-c', "Set-Clipboard '" + _copy_subject.replace("'", "''") + "'"))
|
||||
Popen("sleep 30; powershell.exe -c Set-Clipboard ''", shell=True, stdout=DEVNULL, stderr=DEVNULL)"""
|
||||
clip_replacement = """run(('powershell.exe', '-c', "Set-Clipboard '" + _copy_subject.replace("'", "''") + "'"))
|
||||
Popen((realpath(__file__).rsplit('/', 1)[0] + "/clipclear.py", _hash.hexdigest(), 'wsl'), stdout=DEVNULL, stderr=DEVNULL)"""
|
||||
clear_replacement = """hash_paste.update(run(('powershell.exe', '-c', 'Get-Clipboard'), stdout=PIPE).stdout.strip())
|
||||
if argv[1] == hash_paste.hexdigest():
|
||||
run(('powershell.exe', '-c', 'Set-Clipboard'))"""
|
||||
elif arguments[0] == 'MAC':
|
||||
replacement = """run('pbcopy', stdin=Popen(('printf', _copy_subject.replace('\\\\\\', '\\\\\\\\\\\\\\')
|
||||
.replace('%', '%%')), stdout=PIPE).stdout)
|
||||
Popen("sleep 30; printf '' | pbcopy", shell=True)"""
|
||||
clip_replacement = """run('pbcopy', stdin=Popen(('printf', '%b', _copy_subject.replace('\\\\\\', '\\\\\\\\\\\\\\')), stdout=PIPE).stdout)
|
||||
Popen((realpath(__file__).rsplit('/', 1)[0] + "/clipclear.py", _hash.hexdigest(), 'mac'))"""
|
||||
clear_replacement = """hash_paste.update(run('pbpaste', stdout=PIPE).stdout.strip())
|
||||
if argv[1] == hash_paste.hexdigest():
|
||||
run('pbcopy', input=b'')"""
|
||||
elif arguments[0] == 'HAIKU':
|
||||
replacement = """run(('clipboard', '-c', _copy_subject))
|
||||
Popen('sleep 30; clipboard -r', shell=True)"""
|
||||
clip_replacement = """run(('clipboard', '-c', _copy_subject))
|
||||
Popen((realpath(__file__).rsplit('/', 1)[0] + "/clipclear.py", _hash.hexdigest(), 'haiku'))"""
|
||||
clear_replacement = """hash_paste.update(run(('clipboard', '-p'), stdout=PIPE).stdout.strip())
|
||||
if argv[1] == hash_paste.hexdigest():
|
||||
run(('clipboard', '-r'))"""
|
||||
elif arguments[0] == 'TERMUX':
|
||||
replacement = """run(('termux-clipboard-set', _copy_subject))
|
||||
Popen("sleep 30; termux-clipboard-set ''", shell=True)"""
|
||||
elif arguments[0] == 'LINUX':
|
||||
replacement = """if 'WAYLAND_DISPLAY' in environ:
|
||||
run(('wl-copy', _copy_subject))
|
||||
Popen('sleep 30; wl-copy -c', shell=True)
|
||||
clip_replacement = """run(('termux-clipboard-set', _copy_subject))
|
||||
Popen((realpath(__file__).rsplit('/', 1)[0] + "/clipclear.py", _hash.hexdigest(), 'termux'))"""
|
||||
clear_replacement = """hash_paste.update(run('termux-clipboard-get', stdout=PIPE).stdout.strip())
|
||||
if argv[1] == hash_paste.hexdigest():
|
||||
run(("termux-clipboard-set", "''"))"""
|
||||
elif arguments[0] in ('LINUX', 'BSD'):
|
||||
clip_replacement = """if 'WAYLAND_DISPLAY' in environ:
|
||||
run('wl-copy', stdin=Popen(('printf', '%b', _copy_subject.replace('\\\\\\', '\\\\\\\\\\\\\\')), stdout=PIPE).stdout)
|
||||
Popen((realpath(__file__).rsplit('/', 1)[0] + "/clipclear.py", _hash.hexdigest(), 'wayland'))
|
||||
else:
|
||||
run(('xclip', '-sel', 'c'), stdin=Popen(('printf', _copy_subject
|
||||
.replace('\\\\\\', '\\\\\\\\\\\\\\').replace('%', '%%')), stdout=PIPE).stdout)
|
||||
Popen("sleep 30; printf '' | xclip -sel c", shell=True)"""
|
||||
run(('xclip', '-sel', 'c'), stdin=Popen(('printf', '%b', _copy_subject.replace('\\\\\\', '\\\\\\\\\\\\\\')), stdout=PIPE).stdout)
|
||||
Popen((realpath(__file__).rsplit('/', 1)[0] + "/clipclear.py", _hash.hexdigest(), 'x11'))"""
|
||||
clear_replacement = """if argv[2] == 'wayland':
|
||||
hash_paste.update(run('wl-paste', stdout=PIPE).stdout.strip())
|
||||
if argv[1] == hash_paste.hexdigest():
|
||||
run(('wl-copy', '-c'))
|
||||
else:
|
||||
hash_paste.update(run(('xclip', '-o', '-sel', 'c'), stdout=PIPE).stdout.strip())
|
||||
if argv[1] == hash_paste.hexdigest():
|
||||
run(('xclip', '-i', '/dev/null', '-sel', 'c'))"""
|
||||
else:
|
||||
s_exit()
|
||||
|
||||
# define PORT target
|
||||
string1 = '# PORT START CLIPBOARD'
|
||||
string2 = '# PORT END CLIPBOARD'
|
||||
targets = (('CLIPBOARD', 'sshyp.py', '', '', clip_replacement),
|
||||
('CLIPCLEAR', 'clipclear.py', '\n', '', clear_replacement))
|
||||
|
||||
# read input file
|
||||
text = open('working/sshyp.py', 'r').read()
|
||||
|
||||
# find and replace the defined PORT target
|
||||
regex = re.compile(f"{string1}.*?{string2}", re.DOTALL)
|
||||
new_text = re.sub(regex, replacement, text)
|
||||
|
||||
# write updated text
|
||||
open('working/sshyp.py', 'w').write(new_text)
|
||||
for target in targets:
|
||||
# read text from target file
|
||||
text = open(f"working/{target[1]}", 'r').read()
|
||||
# compile regex and modify text
|
||||
regex = re.compile(f"{target[2]}# PORT START {target[0]}.*?# PORT END {target[0]}{target[3]}", re.DOTALL)
|
||||
new_text = re.sub(regex, target[4], text)
|
||||
# write updated text to target file
|
||||
open(f"working/{target[1]}", 'w').write(new_text)
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
#!/usr/bin/env python3
|
||||
import re
|
||||
|
||||
devtype_replacement = """_install_type = curses_radio(('client (ssh-synchronized)', 'client (offline)'),
|
||||
'device + sync type configuration')"""
|
||||
|
||||
targets = (('TWEAK-EXTEND-FUNCTIONS', 'stweak.py', '\n', '\n\n', ''),
|
||||
('TWEAK-EXTEND-OPTION', 'stweak.py', '', '', "curses_radio(['okay'], "
|
||||
"'extension management is not supported on this platform\\\\n\\\\ninstead, you may install and manage "
|
||||
"extensions through your system package manager\\\\n\\\\nofficial extension packages are available "
|
||||
"at https://github.com/rwinkhart/sshyp-labs/releases')"))
|
||||
|
||||
for target in targets:
|
||||
# read text from target file
|
||||
text = open(f"working/{target[1]}", 'r').read()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
if [ "$1" = 'PREP' ]; then
|
||||
sed -i '1 s/.*/#!\/bin\/env\ python3.10/' ./*.py
|
||||
sed -i '1 s/.*/#!\/bin\/env\ python3.11/' ./*.py
|
||||
else
|
||||
sed -i '1 s/.*/#!\/bin\/env\ python3.10/' ./working/sshyp.py
|
||||
sed -i '1 s/.*/#!\/bin\/env\ python3.11/' ./working/sshyp.py ./working/clipclear.py
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user