Compare commits

..
54 Commits
Author SHA1 Message Date
RandyTheSilly 0bcf5fc0d1 Update documentation for release v1.5.3 2024-01-08 22:16:48 -05:00
RandyTheSilly ec3ee297b8 Remove unnecessary empty variable declarations (these were only included to silence PyCharm warnings) 2024-01-08 22:00:05 -05:00
RandyTheSilly 5f5ede7349 Fix notes sometimes being appended incorrectly when editing incomplete entries 2024-01-08 21:32:01 -05:00
RandyTheSilly cc3fa30865 Add CHOWN.py port-job to package.sh 2024-01-08 20:15:33 -05:00
RandyTheSilly 3613d3dd0c Fix installed extension ownership on FreeBSD 2024-01-08 20:11:26 -05:00
RandyTheSilly 249269a3e3 Address flake8 suggestions 2024-01-08 19:39:44 -05:00
RandyTheSilly c441802058 Fix root-level entries being displayed incorrectly in the entry list 2024-01-08 10:51:59 -05:00
RandyTheSilly 5439c899f7 Update documentation for release v1.5.2 - again 2024-01-07 20:51:41 -05:00
RandyTheSilly 0990bae310 Fix notes starting with blank lines differing in entry readout VS entry preview 2024-01-07 20:20:27 -05:00
RandyTheSilly ae21489c75 Optimize multi-line note readout 2024-01-07 20:00:57 -05:00
RandyTheSilly 5717133168 Do not connect to server from "sshyp init" menu, as the user may not yet have copied their ssh pubkey to the server 2024-01-07 19:42:44 -05:00
RandyTheSilly 07c0dee0a4 Fix quick-unlock: newly generated quick-unlock pins have not been valid since v1.5.0 - this is now fixed - re-generate your quick-unlock pin if it was created after v1.5.0 2024-01-07 19:08:02 -05:00
RandyTheSilly c85640804a Make quick-unlock pin-length configuration helptext more clear 2024-01-07 18:00:11 -05:00
RandyTheSilly 8ec2b31c23 Recommend using ssh keyfile without a password 2024-01-07 17:48:14 -05:00
RandyTheSilly 50189dd526 Warn against removing active device IDs 2024-01-07 17:14:36 -05:00
RandyTheSilly f13404e76f Apply consistent code formatting, especially regarding additive and multiplicative operators 2024-01-07 17:05:18 -05:00
RandyTheSilly 5d00f83df0 Remove trailing spaces upon commit 2024-01-07 16:44:39 -05:00
RandyTheSilly 5ce28e1138 Replace misuses of ".extend()" 2024-01-07 16:33:45 -05:00
RandyTheSilly 2f7b548360 Keep the user in device ID add/remove menus until they select "BACK", optimize menu logic 2024-01-07 16:25:43 -05:00
RandyTheSilly 6789e3e3de Improve method of serving whitelist contents to clients 2024-01-04 14:08:02 -05:00
RandyTheSilly a345f4181c Fix multi-word device IDs being unable to verify via quick-unlock 2024-01-04 13:56:26 -05:00
RandyTheSilly 08c6f1aeef Attempt to remove old device ID registration/whitelist entry from server when changing client device ID - bumps minimum supported Python version to 3.8 2024-01-04 13:41:35 -05:00
RandyTheSilly 3bb2f194d4 Standardize language (vars): _proceed/_sure -> _sure 2024-01-04 03:31:48 -05:00
RandyTheSilly 52727e8eea Extend 18d03279b9 to server tweak menu 2024-01-04 03:23:06 -05:00
RandyTheSilly cd560d6f8f Fix incorrect order of parameters when calling copy_id_check() from dev_id_config(), fixed inconsistency between order of parameters that caused the readability issue leading to the typo 2024-01-04 03:06:02 -05:00
RandyTheSilly 18d03279b9 Standardize language: device name/id -> device id, syncing -> synchronization/synchronizing 2024-01-03 22:56:13 -05:00
RandyTheSilly d4f1831486 Add a warning before changing an existing device id 2024-01-03 22:14:39 -05:00
RandyTheSilly 0ecb842480 Add "BACK" button in optional SSH configuration 2024-01-03 14:58:29 -05:00
RandyTheSilly 45be25e6bd Rename all back/cancel buttons to "BACK" 2024-01-03 14:43:14 -05:00
RandyTheSilly acf46b6227 Move registered_remover() to be covered by RMSERVER port-job 2024-01-03 14:32:46 -05:00
RandyTheSilly 3418bf6170 Replace unnecessary use of pop() 2024-01-03 14:20:15 -05:00
RandyTheSilly dbf3b35237 Add option to remove registered devices in server tweak menu 2024-01-03 14:10:09 -05:00
RandyTheSilly c48edfc624 Add 2024 to copyright duration, split changelog archive for 2024 2024-01-02 14:14:10 -05:00
RandyTheSilly b2eb63cbe6 Update documentation for release v1.5.2 2023-12-11 14:37:01 -05:00
RandyTheSilly 43f8306932 Add processing warnings during gpg key generation 2023-12-11 13:56:56 -05:00
RandyTheSilly e044122d48 Allow displaying partial options in curses menus in smaller terminals 2023-12-11 13:09:21 -05:00
RandyTheSilly 0e45a21a58 Re-implement "additional configuration options" header after init menu 2023-12-11 12:54:01 -05:00
RandyTheSilly a119544fbf Re-implement empty directory notification in entry list 2023-12-11 12:40:10 -05:00
RandyTheSilly 23e51ae133 Improve text wrapping for entry list - list now uses full width of terminal and can be displayed in smaller terminals without crashing 2023-12-11 02:58:04 -05:00
RandyTheSilly 27cf610a1f Fix crash when terminal is not tall enough to display curses radio pretext 2023-12-10 21:49:40 -05:00
RandyTheSilly 2edb1b9b53 Remove "/example/" heading format from extension downloader (this format is reserved entry paths) 2023-12-10 20:10:08 -05:00
RandyTheSilly 675e05c14f Improve text wrapping for curses radio pretext 2023-12-10 19:03:14 -05:00
RandyTheSilly 937b1357f5 Improve entry reader and entry list readability in light terminals 2023-12-10 15:28:57 -05:00
RandyTheSilly c3f8178db4 Fix needless function recursion when calling the global menu from the init menu (fixes errors on Alpine Linux 3.19) 2023-12-09 01:30:19 -05:00
RandyTheSilly 75cf927343 Properly handle error for missing pinentry program/configuration 2023-12-08 20:55:45 -05:00
RandyTheSilly 5a34cfbc8b Bump version to 1.5.2
Former-commit-id: 4344a965a805f5c92a707ed7ad37048f8eda93b6
Former-commit-id: 42325bca647366a6593272694daff9f9af390f10
2023-12-08 19:03:30 -05:00
RandyTheSilly c0d5d3b79f Address 7e1961e0ec [formerly c1521a73d443c173cd2b23f18f71f99fc54b0eb6] [formerly ef5415a793eaf8088f857331872181b40ef4d78f] in port-job
Former-commit-id: fe1d754b62dca302700dd8ab05f01a31117a9d0f
Former-commit-id: 223d95ecba109bad3c0724793c29574600f5bffe
2023-11-27 16:15:25 -05:00
RandyTheSilly d45f4264df Improve license information readability
Former-commit-id: c6c1f1b32544cd43e338c92fed8f8a168690be13
Former-commit-id: 7545104ced2e65a78734184261e104a34b4ec63b
2023-11-22 14:40:58 -05:00
RandyTheSilly dbae72b926 More clearly present extension information in the install screen
Former-commit-id: d75086ed21f45966b6c2b0074aea6e288d1ce650
Former-commit-id: fcf66ffac17cfc97868b0dc6dd4f4c4077f90fd3
2023-10-26 20:30:49 -04:00
RandyTheSilly da4a4d272d Add support for multi-line extension usage information
Former-commit-id: f3dfe5815a3ffcf453b430f12b63235a88469b3f
Former-commit-id: 9c3fec49417dfca2c1494f3edacd123b25dbe27a
2023-10-26 19:11:52 -04:00
RandyTheSilly 7e1961e0ec Fix empty clipboard warnings being shown to the user on Wayland if the same entry field is copied multiple times within 30 seconds
Former-commit-id: ef5415a793eaf8088f857331872181b40ef4d78f
Former-commit-id: c1521a73d443c173cd2b23f18f71f99fc54b0eb6
2023-10-26 18:32:47 -04:00
RandyTheSilly f80fd2b6e2 Fix quotes being copied when clearing Termux clipboard
Former-commit-id: 028b6bdcf891aa017f1d21b6b083591322695b69
Former-commit-id: 9ef4a8e58fc44068efdfab43b6f33e4de76d8efe
2023-10-26 17:48:12 -04:00
RandyTheSilly 787a2bbfc4 Fix extension removal on Busybox environments (Alpine)
Former-commit-id: 47909194ae390b1ace732f19da7a5a72592824c8
Former-commit-id: 165ea2af5edfc99d1094a4aeb1a341ac815d3af8
2023-10-25 21:16:17 -04:00
RandyTheSilly e7f0618142 Fix Fedora and FreeBSD packaging excluding clipclear.py (official v1.5.1 packages are unaffected by this)
Former-commit-id: d8d0d5a4cd14e3aac159ebd9755c82c3f0e058e2
Former-commit-id: 7bc639d5628c27988f88cb691345eb8680c07266
2023-10-24 11:50:26 -04:00
13 changed files with 499 additions and 267 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
![sshyp](https://github.com/rwinkhart/sshyp-labs/blob/main/extra/artwork/sshyp-banner.png)
[![release](https://img.shields.io/github/v/release/rwinkhart/sshyp)](https://github.com/rwinkhart/sshyp/releases)
![python](https://img.shields.io/badge/python-3.7--3.12-yellow)
![python](https://img.shields.io/badge/python-3.8--3.12-yellow)
[![downloads](https://img.shields.io/github/downloads/rwinkhart/sshyp/total)](https://github.com/rwinkhart/sshyp/releases)
[![CodeQL](https://github.com/rwinkhart/sshyp/actions/workflows/codeql-analysis.yml/badge.svg?branch=main)](https://github.com/rwinkhart/sshyp/actions/workflows/codeql-analysis.yml)
@@ -14,7 +14,7 @@ sshyp is compatible with entries created by pass/password-store, as its original
sshyp makes use of a custom sftp wrapper, called sshync (ssh+sync), to reliably sync user entries with a local or remote server.
The name "sshyp" is a combination of its syncing library, "sshync", and "passwords".
The name "sshyp" is a combination of its synchronization library, "sshync", and "passwords".
# WARNING
It is your responsibility to assess the security and stability of "sshyp" before using it and ensure it meets your needs.
+1
View File
@@ -1,4 +1,5 @@
#!/bin/sh
sed -i 's/[[:space:]]*$//' ./lib/* ./port-jobs/* ./package.sh
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
+82
View File
@@ -0,0 +1,82 @@
sshyp v1.5.3
01/08/2024
the fortified flock update - patch three
this release is a quick hot-fix for some bugs that were noticed after the release of v1.5.2
fixes/optimizations:
- fixed a regression with the entry list causing entries not part of a subdirectory to appear
in-line with the entry list header
- fixed extension group ownership under FreeBSD
^ the "wheel" group is now used where previously the non-existent "root" group was used
- fixed notes sometimes being appended in the wrong position after editing an incomplete entry
- addressed flake8 suggestions (slight optimization)
- removed unnecessary empty variable declarations
^ these were previously included to silence PyCharm's PEP8 warnings
^ the project is now primarily developed in VSCodium with the flake8 linter
^ the only change to flake8 defaults in the line length limit (79->120)
<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
sshyp v1.5.2
01/07/2024
the fortified flock update - patch two
this release focuses on fixing various bugs, crashes, and visual oddities;
additionally, the new-user setup experience has been vastly improved
compatibility-breaking changes:
- this release is fully compatible with sshyp v1.5.0-v1.5.1
- the minimum Python version has been raised from Python 3.7 to Python 3.8
^ this was needed for the new device id changing behavior
^ Python 3.7 no longer receives security updates, so this should have minimal impact
^ please create an issue on GitHub if any Python version (3.8 or newer) does not work as intended
user-facing features:
- registered device ids can now be removed from the server tweak menu
^ all device id menus now stay open until the user manually exits
- changing a client's device id now attempts to automatically remove the previous id from the server
- the extension downloader now supports showing multiple lines of usage information
^ sshyp-mfa is taking advantage of this to display the info from the old man page
- basic tweak/init menu text wrapping and terminal-resizing has been added
^ the tweak/init radio menus now warn if the terminal needs to be resized to see all content;
this resizing can be done in real time without restarting sshyp (layout dynamically adjusts)
^ this avoids crashes on large radio menus in small terminals (crashes are still possible, but
only on unreasonably small terminals - this will probably stay the way it is now)
- ssh connections are no longer made from the init menu
^ this is to give the user time to register their ssh pubkey with the server
- the user is now warned that gpg key generation may take time and that sshyp should not be terminated
- the user is now warned about risks associated with changing or removing active device ids
- back buttons have been added to menus that were missing a safe way to exit
fixes/optimizations:
- quick-unlock has been fixed - invalid pins have been generated since v1.5.0
^ if quick-unlock is not working for you, re-generate your pin after this update!
- extension removal now works on Busybox environments (Alpine Linux)
- instead of crashing with an unhelpful error, sshyp now warns if a valid pinentry program is not found (gpg)
^ affects some Alpine Linux 3.19 base installs (pinentry does not always get installed with gpg)
- a recursive curses.wrapper() function is no longer used (fixes menu crash on Alpine Linux 3.19)
^ the new method of transitioning menus is more efficient on all platforms
- the wl-paste empty clipboard warning is no longer shown when multiple copies are done within 30 seconds (Wayland)
- quotes are no longer copied to the Termux clipboard on clear
- the entry reader and entry list should now be more visible in light terminals
^ in testing, I noticed some solarized dark themes bind color 8 to the same color as the terminal background,
leading to some invisible text - this will not be fixed (unless custom accent colors are added), as
I believe this is poor theme design and the fault of the particular solarized dark themes I tested
- entry list text wrapping has been vastly improved
^ better utilization of full terminal width
^ less prone to splitting entry names in half (should be impossible now)
^ no longer crashes on small terminals
- multi-word device ids can now verify via quick-unlock
- various language standardization changes
^ all back buttons in menus are now named "BACK"
^ all mentions of device ids are now referred to as "ids", rather than "names"
^ various non-user-facing variable renamings have also been made
- various menu helptext have been made more clear
^ this includes quick-unlock configuration, ssh configuration, device id configuration, and more
+2 -2
View File
@@ -1,4 +1,4 @@
.TH sshyp 1 "24 October 2023" "v1.5.1" "sshyp man page"
.TH sshyp 1 "08 January 2024" "v1.5.3" "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
@@ -38,7 +38,7 @@ Making a new entry saved as '~/.local/share/sshyp/school/university.gpg' using t
Creating a note-only entry saved as '~/.local/share/sshyp/notes/test note.gpg':
sshyp /notes/test\ note add -n
Manually syncing entries with the server:
Manually synchronizing entries with the server:
sshyp sync
.SH ARGUMENTS (CLIENT)
help/-h bring up the help menu
+3 -3
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
from hashlib import sha512
from subprocess import PIPE, run
from subprocess import DEVNULL, PIPE, run
from sys import argv
from time import sleep
@@ -16,7 +16,7 @@ if argv[2] == 'wsl':
run(('powershell.exe', '-c', 'Set-Clipboard'))
elif argv[2] == 'wayland':
hash_paste.update(run('wl-paste', stdout=PIPE).stdout.strip())
hash_paste.update(run('wl-paste', stdout=PIPE, stderr=DEVNULL).stdout.strip())
if argv[1] == hash_paste.hexdigest():
run(('wl-copy', '-c'))
@@ -33,7 +33,7 @@ elif argv[2] == 'mac':
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", "''"))
run(("termux-clipboard-set", ''))
elif argv[2] == 'x11':
hash_paste.update(run(('xclip', '-o', '-sel', 'c'), stdout=PIPE).stdout.strip())
+7 -7
View File
@@ -8,12 +8,12 @@ home = expanduser('~')
# PORT START SSHYNC-REMOTE
# REMOTE
# prints all necessary remote data to stdout
def remote_list_gen(_client_device_name, _remote_dir):
def remote_list_gen(_client_device_id, _remote_dir):
# deletions
for _file in listdir(f"{home}/.config/sshyp/deleted"):
_file_path, _sep, _device = _file.partition('\x1f')
_file_path = _file_path.replace('\x1e', '/')
if _device == _client_device_name:
if _device == _client_device_id:
print(_file_path)
try:
remove(f"{home}/.config/sshyp/deleted/{_file}")
@@ -31,7 +31,7 @@ def remote_list_gen(_client_device_name, _remote_dir):
# HYBRID
# deletes a file or folder and/or marks it for deletion upon syncing
# deletes a file or folder and/or marks it for deletion upon synchronizing
def delete(_file_path, _target_database, _silent):
from shutil import rmtree
_directory = f"{home}/.local/share/sshyp/"
@@ -44,8 +44,8 @@ def delete(_file_path, _target_database, _silent):
if not _silent:
print(f"location does not exist {_target_database}")
if _target_database == 'remotely':
for _device_name in listdir(f"{home}/.config/sshyp/devices"):
open(f"{home}/.config/sshyp/deleted/" + _file_path.replace('/', '\x1e') + '\x1f' + _device_name, 'w')
for _device_id in listdir(f"{home}/.config/sshyp/devices"):
open(f"{home}/.config/sshyp/deleted/" + _file_path.replace('/', '\x1e') + '\x1f' + _device_id, 'w')
# retrieves and returns titles and mod times from the local device
@@ -78,8 +78,8 @@ def remote_list_fetch(_user_data):
_deletion_database = _remote_data[0].strip().splitlines()
_folder_database = _remote_data[1].strip().splitlines()
_titles_mods = _remote_data[2].strip().splitlines()
return _deletion_database, _folder_database, _titles_mods[:len(_titles_mods)//2], \
_titles_mods[len(_titles_mods)//2:]
return _deletion_database, _folder_database, _titles_mods[:len(_titles_mods) // 2], \
_titles_mods[len(_titles_mods) // 2:]
# checks for and acts upon files and folders marked for deletion
+120 -125
View File
@@ -18,29 +18,32 @@ home = expanduser('~')
# generates and prints full entry list
def entry_list_gen(_directory=f"{home}/.local/share/sshyp/"):
from shutil import get_terminal_size
from textwrap import fill
_ran = False
print("\nfor a list of usable commands, run 'sshyp help'\n\n\u001b[38;5;0;48;5;15msshyp entries:\u001b[0m\n")
_ran, _width = False, get_terminal_size().columns
print("\nfor a list of usable commands, run 'sshyp help'\n\n\u001b[38;5;0;48;5;15msshyp entries:\u001b[0m", end='')
for _root, _dirs, _files in sorted(walk(_directory, topdown=True)):
_entry_list, _color_alternator = [], 1
_color_alternator = 1
if _ran:
print(f"\u001b[38;5;15;48;5;238m{_root.replace(f'{home}/.local/share/sshyp', '', 1)}/\u001b[0m")
for filename in sorted(_files):
print(f"\n\n\u001b[38;5;7;48;5;8m{_root.replace(f'{home}/.local/share/sshyp', '', 1)}/\u001b[0m")
elif _root == f"{home}/.local/share/sshyp/" and len(_files) > 0:
print("\n\n\u001b[38;5;7;48;5;8m/\u001b[0m")
_char_counter = 0
for _filename in sorted(_files):
if _color_alternator > 0:
_entry_list.append(filename[:-4])
_print_string = _filename[:-4]
else:
_entry_list.append(f"\u001b[38;5;8m{filename[:-4]}\u001b[0m")
_print_string = f"\u001b[38;5;8m{_filename[:-4]}\u001b[0m"
# -3 instead of -4 to account for trailing space character
_char_counter += len(_filename) - 3
if _char_counter >= _width:
# reset _char_counter to length of first entry in new line
_char_counter = len(_filename) - 3
print()
print(_print_string + ' ', end='')
_color_alternator = _color_alternator * -1
_real = len(' '.join(_entry_list)) - (5.5 * len(_entry_list))
if _real <= get_terminal_size()[0]:
_width = len(' '.join(_entry_list))
else:
_width = (len(' '.join(_entry_list)) / (_real / get_terminal_size()[0]) - 25)
if len(_entry_list) > 0:
print(fill(' '.join(_entry_list), width=_width) + '\n')
elif _ran:
print('\u001b[38;5;9m-empty directory-\u001b[0m\n')
if _ran and _char_counter < 1:
print('\u001b[38;5;9m-empty directory-\u001b[0m', end='')
_ran = True
print('\n')
# displays the contents of an entry in a readable format
@@ -49,29 +52,29 @@ def entry_reader(_decrypted_entry):
if pass_show:
_entry_password = f'\u001b[38;5;10m{_decrypted_entry[0]}\u001b[0m'
else:
_entry_password = f'\u001b[38;5;3mend command in "--show" or "-s" to view\u001b[0m'
_entry_password = '\u001b[38;5;3mend command in "--show" or "-s" to view\u001b[0m'
print()
for _num in range(len(_decrypted_entry)):
try:
if _num == 0 and _decrypted_entry[1] != '':
print(f"\u001b[38;5;15;48;5;238musername:\u001b[0m\n{_decrypted_entry[1]}\n")
print(f"\u001b[38;5;7;48;5;8musername:\u001b[0m\n{_decrypted_entry[1]}\n")
elif _num == 1 and _decrypted_entry[0] != '':
print(f"\u001b[38;5;15;48;5;238mpassword:\u001b[0m\n{_entry_password}\n")
print(f"\u001b[38;5;7;48;5;8mpassword:\u001b[0m\n{_entry_password}\n")
elif _num == 2 and _decrypted_entry[2] != '':
print(f"\u001b[38;5;15;48;5;238murl:\u001b[0m\n{_decrypted_entry[_num]}\n")
elif _num >= 3 and _decrypted_entry[_num] != '' and _notes_flag != 1:
_notes_flag = 1
print('\u001b[38;5;15;48;5;238mnotes:\u001b[0m\n' + _decrypted_entry[_num])
elif _num >= 3 and _notes_flag == 1:
print(f"\u001b[38;5;7;48;5;8murl:\u001b[0m\n{_decrypted_entry[_num]}\n")
elif _notes_flag == 1 and _num >= 3:
print(_decrypted_entry[_num])
elif _notes_flag != 1 and _num >= 3 and _decrypted_entry[_num] != '':
_notes_flag = 1
print('\u001b[38;5;7;48;5;8mnotes:\u001b[0m\n' + _decrypted_entry[_num])
if _notes_flag == 1:
try:
_line_test = _decrypted_entry[_num + 1]
_ = _decrypted_entry[_num + 1]
except IndexError:
print()
except IndexError:
if _num == 0 and _decrypted_entry[0] != '':
print(f"\u001b[38;5;15;48;5;238mpassword:\u001b[0m\n{_entry_password}\n")
print(f"\u001b[38;5;7;48;5;8mpassword:\u001b[0m\n{_entry_password}\n")
# generates and returns a random string based on input
@@ -81,7 +84,7 @@ def string_gen(_complexity, _length):
if _complexity == 's':
_character_pool = string.ascii_letters + string.digits
elif _complexity == 'f':
_character_pool = string.digits + string.ascii_letters + string.punctuation.replace('/', '').replace('\\', '')\
_character_pool = string.digits + string.ascii_letters + string.punctuation.replace('/', '').replace('\\', '') \
.replace("'", '').replace('"', '').replace('`', '').replace('~', '')
else:
_character_pool = string.digits + string.ascii_letters + string.punctuation
@@ -135,17 +138,15 @@ def edit_note(_note_lines, _exit_on_match=False):
return _new_note
# encrypts an entry and cleans up the temporary files
# encrypts an entry
def encrypt(_entry_data, _entry_dir, _gpg_id):
_bytes_data = '\n'.join(_entry_data).rstrip().encode()
_encrypted_data = run(('gpg', '-qr', str(_gpg_id), '-e'), input=_bytes_data, stdout=PIPE).stdout
open(_entry_dir + '.gpg', 'wb').write(_encrypted_data)
# decrypts an entry to a temporary directory
# decrypts an entry and returns its contents
def decrypt(_entry_dir, _quick_verify=None, _quick_pass=None):
_contents = None
# check quick-unlock status, fetch passphrase
if _quick_verify:
_quick_pass = whitelist_verify(port, username_ssh, ip, client_device_id, identity)
@@ -192,14 +193,15 @@ def whitelist_verify(_port, _username_ssh, _ip, _client_device_id, _identity):
except CalledProcessError:
_i, _full_password = 0, ''
_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()
'python3 -c \'from os import listdir; [print(_id, end="\x1f") for _id in '
f'listdir("/home/{_username_ssh}/.config/sshyp/whitelist")]\''),
stdout=PIPE, text=True).stdout.rstrip().split('\x1f')
for _device_id in _server_whitelist:
if _device_id == _client_device_id:
from getpass import getpass
_quick_unlock_password = getpass(prompt='\nquick-unlock pin: ')
_quick_unlock_password_excluded = \
run(('ssh', '-i', _identity, '-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):
@@ -230,7 +232,7 @@ def target_exists_check(_target_name, _expected_presence):
else:
print(f"\n\u001b[38;5;9merror: (/{_target_name}) does not exist\u001b[0m\n")
s_exit(2)
# returns target type (entry == True, folder == False, null == None), optional errors
def target_type_check(_target_name, _expected_type=True, _error=False):
@@ -249,17 +251,18 @@ def target_type_check(_target_name, _expected_type=True, _error=False):
s_exit(2)
# ensures an edited entry is optimized for best compatibility
def line_edit(_lines, _edit_data, _edit_line):
# ensures an entry has enough lines to complete an edit and optionally carries out the edit
def line_edit(_lines, _edit_line, _edit_data=None):
# ensure enough lines are present for edited field
while len(_lines) < _edit_line + 1:
_lines.append('')
# write the edited field
_lines[_edit_line] = _edit_data.rstrip()
if _edit_data is not None:
# write the edited field
_lines[_edit_line] = _edit_data.rstrip()
return _lines
# 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 synchronization
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'):
@@ -270,8 +273,8 @@ def copy_id_check(_port, _username_ssh, _ip, _client_device_id, _identity, _sshy
f'{_client_device_id}").touch(mode=0o400, exist_ok=True)\''), stderr=DEVNULL, check=True)
except CalledProcessError:
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')
'registered on the remote server and that the entered ip, port, and username are correct\n\n'
'synchronization functionality will be disabled until this is addressed\u001b[0m\n')
_sshyp_data.set('CLIENT-ONLINE', 'ssh_error', 'true')
write_config(_sshyp_data)
return True
@@ -285,101 +288,101 @@ def copy_id_check(_port, _username_ssh, _ip, _client_device_id, _identity, _sshy
# prints help text based on argument
def print_info():
if arguments[0] in ('version', '-v'):
_blank = '\u001b[38;5;7;48;5;8m\\\u001b[38;5;15;48;5;15m' + 55*' ' + '\u001b[38;5;7;48;5;8m/\u001b[0m'
_border = '\u001b[38;5;7;48;5;8m' + 14*'<>' + '-' + 14*'<>' + '\u001b[0m\n'
_blank = '\u001b[38;5;7;48;5;8m\\\u001b[38;5;15;48;5;15m' + 55 * ' ' + '\u001b[38;5;7;48;5;8m/\u001b[0m'
_border = '\u001b[38;5;7;48;5;8m' + 14 * '<>' + '-' + 14 * '<>' + '\u001b[0m\n'
print(f"""\nsshyp is a simple, self-hosted, sftp-synchronized\npassword manager for unix(-like) systems\n
{9*' '}..{15*' '}\u001b[38;5;12m \u001b[38;5;9m\u001b[0m{15*' '}..
{8*' '}/()\\''.''.{7*' '}\u001b[38;5;12m\u001b[0m\u001b[0m{7*' '}.''.''/()\\{3*' '}_)
{5*' '}_.{3*' '}:{7*' '}*{7*' '}\u001b[38;5;9m\u001b[0m{7*' '}*{7*' '}:{3*' '}<[]|_|=
}}-}}-*]{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.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 ",
f"randall winkhart\u001b[38;5;15;48;5;15m{9*' '}\u001b[38;5;7;48;5;8m/\u001b[0m")
{9 * ' '}..{15 * ' '}\u001b[38;5;12m \u001b[38;5;9m\u001b[0m{15 * ' '}..
{8 * ' '}/()\\''.''.{7 * ' '}\u001b[38;5;12m\u001b[0m\u001b[0m{7 * ' '}.''.''/()\\{3 * ' '}_)
{5 * ' '}_.{3 * ' '}:{7 * ' '}*{7 * ' '}\u001b[38;5;9m\u001b[0m{7 * ' '}*{7 * ' '}:{3 * ' '}<[]|_|=
}}-}}-*]{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.3\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-2024 ",
f"randall winkhart\u001b[38;5;15;48;5;15m{9 * ' '}\u001b[38;5;7;48;5;8m/\u001b[0m")
print(f"{_blank}\n{_border}\nsee https://github.com/rwinkhart/sshyp for more information\n")
elif arguments[0] == 'license':
print('\nThis program is free software: you can redistribute it and/or modify it under the terms\nof version 3 '
print('\nThis program is free software: you can redistribute it and/or modify it under the terms of\nversion 3 '
'(only) of the GNU General Public License as published by the Free Software Foundation.\n\nThis program '
'is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\nwithout even the implied '
'warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\nSee the GNU General Public License for'
' more details.\n\nhttps://opensource.org/licenses/GPL-3.0\n')
'warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License '
'for more details:\nhttps://opensource.org/licenses/GPL-3.0\n')
elif arguments[0] == 'add' and device_type == 'client':
print(f"""\n\u001b[1musage:\u001b[0m sshyp /<entry name> add <option>\u001b[0m\n
\u001b[1moptions:\u001b[0m
add:
password/-p{12*' '}add a password entry
note/-n{16*' '}add a note entry
folder/-f{14*' '}add a new folder for entries\n""")
password/-p{12 * ' '}add a password entry
note/-n{16 * ' '}add a note entry
folder/-f{14 * ' '}add a new folder for entries\n""")
elif arguments[0] == 'edit' and device_type == 'client':
print(f"""\n\u001b[1musage:\u001b[0m sshyp /<entry name> edit <option>\u001b[0m\n
\u001b[1moptions:\u001b[0m
edit:
rename/relocate/-r{5*' '}rename or relocate an entry
username/-u{12*' '}change the username of an entry
password/-p{12*' '}change the password of an entry
url/-l{17*' '}change the url attached to an entry
note/-n{16*' '}change the note attached to an entry\n""")
rename/relocate/-r{5 * ' '}rename or relocate an entry
username/-u{12 * ' '}change the username of an entry
password/-p{12 * ' '}change the password of an entry
url/-l{17 * ' '}change the url attached to an entry
note/-n{16 * ' '}change the note attached to an entry\n""")
elif arguments[0] == 'copy' and device_type == 'client':
print(f"""\n\u001b[1musage:\u001b[0m sshyp /<entry name> copy <option>\u001b[0m\n
\u001b[1moptions:\u001b[0m
copy:
username/-u{12*' '}copy the username of an entry to your clipboard
password/-p{12*' '}copy the password of an entry to your clipboard
url/-l{17*' '}copy the url of an entry to your clipboard
note/-n{16*' '}copy the note of an entry to your clipboard\n""")
username/-u{12 * ' '}copy the username of an entry to your clipboard
password/-p{12 * ' '}copy the password of an entry to your clipboard
url/-l{17 * ' '}copy the url of an entry to your clipboard
note/-n{16 * ' '}copy the note of an entry to your clipboard\n""")
elif arguments[0] == 'gen' and device_type == 'client':
print(f"""\n\u001b[1musage:\u001b[0m sshyp /<entry name> gen [option]\u001b[0m\n
\u001b[1moptions:\u001b[0m
gen:
update/-u{14*' '}generate a password for an existing entry\n""")
update/-u{14 * ' '}generate a password for an existing entry\n""")
else:
print("\n\u001b[1msshyp ", "copyright (c) 2021-2023 ", """randall winkhart\u001b[0m
print("\n\u001b[1msshyp ", "copyright (c) 2021-2024 ", """randall winkhart\u001b[0m
this is free software, and you are welcome to redistribute it under certain conditions;
this program comes with absolutely no warranty; type 'sshyp license' for details""")
if device_type == 'client':
print(f"""\n\u001b[1musage:\u001b[0m sshyp [/<entry name> [argument] [option]] | [argument]\n
\u001b[1marguments:\u001b[0m
help/-h{17*' '}bring up this menu
version/-v{14*' '}display sshyp version info
init{20*' '}set up sshyp
tweak{19*' '}change configuration options/manage extensions and updates
add{21*' '}add an entry
gen{21*' '}generate a new password
edit{20*' '}edit an existing entry
copy{20*' '}copy details of an entry to your clipboard
shear{19*' '}delete an existing entry
sync{20*' '}manually sync the entry directory via sshync
help/-h{17 * ' '}bring up this menu
version/-v{14 * ' '}display sshyp version info
init{20 * ' '}set up sshyp
tweak{19 * ' '}change configuration options/manage extensions and updates
add{21 * ' '}add an entry
gen{21 * ' '}generate a new password
edit{20 * ' '}edit an existing entry
copy{20 * ' '}copy details of an entry to your clipboard
shear{19 * ' '}delete an existing entry
sync{20 * ' '}manually sync the entry directory via sshync
\n\u001b[1moptions:\u001b[0m
add:
password/-p{12*' '}add a password entry
note/-n{16*' '}add a note entry
folder/-f{14*' '}add a new folder for entries
password/-p{12 * ' '}add a password entry
note/-n{16 * ' '}add a note entry
folder/-f{14 * ' '}add a new folder for entries
edit:
rename/relocate/-r{5*' '}rename or relocate an entry
username/-u{12*' '}change the username of an entry
password/-p{12*' '}change the password of an entry
url/-l{17*' '}change the url attached to an entry
note/-n{16*' '}change the note attached to an entry
rename/relocate/-r{5 * ' '}rename or relocate an entry
username/-u{12 * ' '}change the username of an entry
password/-p{12 * ' '}change the password of an entry
url/-l{17 * ' '}change the url attached to an entry
note/-n{16 * ' '}change the note attached to an entry
copy:
username/-u{12*' '}copy the username of an entry to your clipboard
password/-p{12*' '}copy the password of an entry to your clipboard
url/-l{17*' '}copy the url of an entry to your clipboard
note/-n{16*' '}copy the note of an entry to your clipboard
username/-u{12 * ' '}copy the username of an entry to your clipboard
password/-p{12 * ' '}copy the password of an entry to your clipboard
url/-l{17 * ' '}copy the url of an entry to your clipboard
note/-n{16 * ' '}copy the note of an entry to your clipboard
gen:
update/-u{14*' '}generate a password for an existing entry
update/-u{14 * ' '}generate a password for an existing entry
\n\u001b[1mtip 1:\u001b[0m you can quickly read an entry with 'sshyp /<entry name>'
\u001b[1mtip 2:\u001b[0m type 'sshyp' to view a list of saved entries\n""")
# PORT START HELP-SERVER
else:
print(f"""\n\u001b[1musage:\u001b[0m sshyp <argument>\n
\u001b[1marguments:\u001b[0m
help/-h{17*' '}bring up this menu
version/-v{14*' '}display sshyp version info
init{20*' '}set up sshyp
tweak{19*' '}change configuration options/manage extensions and updates\n""")
help/-h{17 * ' '}bring up this menu
version/-v{14 * ' '}display sshyp version info
init{20 * ' '}set up sshyp
tweak{19 * ' '}change configuration options/manage extensions and updates\n""")
# PORT END HELP-SERVER
@@ -391,7 +394,7 @@ def read_shortcut():
# calls sshync to sync changes to the user's server
def sync(_start_text=''):
print(f"{_start_text}syncing entries with the server device...\n")
print(f"{_start_text}synchronizing entries with the server device...\n")
# set permissions before uploading
for _root, _dirs, _files in walk(f"{home}/.local/share/sshyp"):
for _path in _root.splitlines():
@@ -420,7 +423,7 @@ def add_entry():
else:
_note = ''
print('\n\u001b[1mentry preview:\u001b[0m')
entry_reader([_password, _username, _url, _note])
entry_reader([_password, _username, _url] + _note.split('\n'))
encrypt([_password, _username, _url, _note], directory + entry_name, gpg_id)
@@ -459,7 +462,7 @@ def rename():
Path(f"{directory}{_new_name}").mkdir(mode=0o700, parents=True, exist_ok=True)
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}"))\''))
f'.rename(Path("{directory_ssh}{_new_name}"))\''))
else:
move(f"{directory}{entry_name}", f"{directory}{_new_name}")
if not ssh_error:
@@ -469,9 +472,6 @@ def rename():
# edits the contents of an entry
def edit():
# set to avoid PEP8 warnings
_detail, _edit_line = None, None
# ensure the edit target is an entry
target_type_check(entry_name, True, True)
@@ -484,10 +484,10 @@ def edit():
_detail, _edit_line = str(input('\nurl: ')), 2
if arguments[2] in ('note', '-n'):
_old_lines = decrypt(directory + entry_name, _quick_verify=quick_unlock_enabled)
_new_lines = _old_lines[0:3] + edit_note(_old_lines[3:], True).split('\n')
# pass 2 as _edit_line to simulate a full entry minus notes so that notes are appended correctly
_new_lines = line_edit(_old_lines[0:3], 2) + edit_note(_old_lines[3:], True).split('\n')
else:
_new_lines = line_edit(decrypt(directory + entry_name, _quick_verify=quick_unlock_enabled), _detail,
_edit_line)
_new_lines = line_edit(decrypt(directory + entry_name, _quick_verify=quick_unlock_enabled), _edit_line, _detail)
print('\n\u001b[1mentry preview:\u001b[0m')
entry_reader(_new_lines)
encrypt(_new_lines, directory + entry_name, gpg_id)
@@ -495,13 +495,11 @@ def edit():
# generates a password for a new or an existing entry
def gen():
# set to avoid PEP8 warnings
_username, _url, _notes = None, None, None
# gen update
if arg_count == 3 and arguments[2] in ('update', '-u'):
# ensure the gen update target is an entry
# ensure the gen update target is an entry
target_type_check(entry_name, True, True)
_new_lines = line_edit(decrypt(directory + entry_name, _quick_verify=quick_unlock_enabled), pass_gen(), 0)
_new_lines = line_edit(decrypt(directory + entry_name, _quick_verify=quick_unlock_enabled), 0, pass_gen())
# gen
else:
# make sure the gen target does not already exist
@@ -513,7 +511,7 @@ def gen():
_note = edit_note([])
else:
_note = ''
_new_lines = [_password, _username, _url, _note]
_new_lines = [_password, _username, _url] + _note.split('\n')
print('\n\u001b[1mentry preview:\u001b[0m')
entry_reader(_new_lines)
encrypt(_new_lines, directory + entry_name, gpg_id)
@@ -611,10 +609,7 @@ if __name__ == "__main__":
try:
# set default states
ssh_error, success_flag, sync_flag, silent_sync, pass_show = False, False, False, False, False
# set to avoid PEP8 warnings
arg_start, device_type, offline_mode_enabled = None, None, None
# retrieve typed argument
arguments = argv[1:]
arg_count = len(arguments)
@@ -625,8 +620,8 @@ if __name__ == "__main__":
arg_start = 1
entry_name = arguments[0].strip('/')
# determine whether to show passwords in entry previews
if arg_count > 1 and arguments[arg_count-1] in ('--show', '-s'):
arguments.pop()
if arg_count > 1 and arguments[arg_count - 1] in ('--show', '-s'):
del arguments[-1]
arg_count -= 1
pass_show = True
else:
@@ -657,13 +652,13 @@ if __name__ == "__main__":
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(f"\n{73 * '!'}")
print("not all necessary configurations have been made - please run 'sshyp init'")
print(f"{73*'!'}\n")
print(f"{73 * '!'}\n")
s_exit(1)
else:
from stweak import wrapped_entry
wrapped_entry(False)
wrapped_entry(False, 'additional configuration options:')
s_exit()
# run function based on arguments
@@ -714,14 +709,14 @@ if __name__ == "__main__":
success_flag = True
from stweak import wrapped_entry
wrapped_entry(device_type)
if arg_count > 0 and success_flag == 0 and arguments[0] != 'sync':
if arguments[0] not in ('help', '-h', 'version', '-v', 'license'):
extension_runner()
else:
print_info()
elif not ssh_error:
if sync_flag:
if sync_flag:
sync()
elif (arg_count > 0 and arguments[0] == 'sync') or (arg_count > 1 and arguments[1] == 'shear'):
sync('\n')
+241 -123
View File
@@ -5,12 +5,12 @@ from os import environ, listdir, remove
from os.path import exists, expanduser, isfile
from pathlib import Path
from random import randint
from shutil import get_terminal_size, which
from subprocess import PIPE, run
from shutil import which
from subprocess import CalledProcessError, DEVNULL, PIPE, run
# PORT START UNAME-IMPORT-STWEAK
from os import uname
# PORT END UNAME-IMPORT-STWEAK
home, sshyp_data, stdscr = expanduser('~'), ConfigParser(interpolation=None), None
home, sshyp_data, stdscr, gm_device_type = expanduser('~'), ConfigParser(interpolation=None), None, None
if isfile(f"{home}/.config/sshyp/sshyp.ini"):
_exists_flag = True
sshyp_data.read(f"{home}/.config/sshyp/sshyp.ini")
@@ -28,25 +28,62 @@ def write_config(_sshyp_data=sshyp_data):
def curses_radio(_options, _pretext):
curs_set(0)
_selected = 0
while True:
# clear curses window
stdscr.clear()
stdscr.addstr(0, 0, _pretext)
for _i, _option in enumerate(_options):
_y = _i + _pretext.count('\n') + 2
if _i == _selected:
stdscr.addstr(_y, 0, "[*] " + _option, A_REVERSE)
# get terminal size
_height, _width = stdscr.getmaxyx()
# track whether to generate interactive buttons (depending on terminal size)
_button_gen = False
# split text based on new lines
_pretext_lines = _pretext.split('\n')
# iterate through lines, splitting further (wrapping) as needed, to add to curses window
_current_line = 0
for _line in _pretext_lines:
if _current_line <= _height - 4:
_button_gen = True
while len(_line) > _width:
stdscr.addstr(_current_line, 0, _line[:_width])
_line = _line[_width:]
_current_line += 1
stdscr.addstr(_current_line, 0, _line)
_current_line += 1
else:
stdscr.addstr(_y, 0, "[ ] " + _option)
stdscr.refresh()
_key = stdscr.getch()
_button_gen = False
stdscr.addstr(_current_line, 0, '# warning: re-size terminal to see more information'[:_width - 1])
_current_line += 1
break
# create user-interactive options if all information has been displayed to the user
if _button_gen:
for _i, _option in enumerate(_options):
_y = _i + _current_line + 1
if _y < _height:
if _i == _selected:
stdscr.addstr(_y, 0, "[*] " + _option, A_REVERSE)
else:
stdscr.addstr(_y, 0, "[ ] " + _option)
else:
stdscr.addstr(_y - 1, 0, '# warning: re-size terminal to see more information')
break
# update _selected based on user input
if _key == KEY_UP:
_selected = (_selected-1) % len(_options)
elif _key == KEY_DOWN:
_selected = (_selected+1) % len(_options)
elif _key == ord('\n'):
break
_key = stdscr.getch()
if _button_gen:
if _key == KEY_UP:
_selected = (_selected - 1) % len(_options)
elif _key == KEY_DOWN:
_selected = (_selected + 1) % len(_options)
# ord('\n') == 10
elif _key == 10:
break
stdscr.refresh()
curs_set(1)
return _selected
@@ -55,9 +92,9 @@ def curses_radio(_options, _pretext):
def curses_text(_pretext):
stdscr.clear()
stdscr.addstr(0, 0, _pretext)
_term_columns = get_terminal_size()[0]
_editwin = newwin(1, _term_columns-2, 3, 1)
rectangle(stdscr, 2, 0, 4, _term_columns-1)
_width = stdscr.getmaxyx()[1]
_editwin = newwin(1, _width - 2, 3, 1)
rectangle(stdscr, 2, 0, 4, _width - 1)
stdscr.refresh()
_box = Textbox(_editwin)
# let the user edit until ctrl+g/enter is struck
@@ -102,13 +139,22 @@ def gpg_config():
_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:
if _gpg_id_sel == len(_named_uid_list) - 1:
if not isfile(f"{home}/.config/sshyp/gpg-gen"):
open(f"{home}/.config/sshyp/gpg-gen", 'w').writelines([
'Key-Type: 1\n', 'Key-Length: 4096\n', 'Key-Usage: sign encrypt\n', 'Name-Real: sshyp\n',
'Name-Comment: gpg-sshyp\n', 'Name-Email: github.com/rwinkhart/sshyp\n',
'Expire-Date: 0'])
run(('gpg', '-q', '--batch', '--generate-key', f"{home}/.config/sshyp/gpg-gen"))
curses_radio(['okay'], 'gpg key generation may take some time (especially on slower devices)\n\nselect "okay" '
'to start\n\ndo not terminate this process!')
try:
run(('gpg', '-q', '--batch', '--generate-key', f"{home}/.config/sshyp/gpg-gen"), stderr=PIPE, check=True)
except CalledProcessError as e:
if 'No pinentry' in e.stderr.decode("utf-8"):
curses_radio(['okay'], 'either a valid pinentry program is missing or gpg is not configured to use an '
'available pinentry program\n\nsshyp will now exit')
from sys import exit as s_exit
s_exit(6)
remove(f"{home}/.config/sshyp/gpg-gen")
_gpg_id = run(('gpg', '-k', '--with-colons'), stdout=PIPE, text=True).stdout.splitlines()[-1].split(':')[9]
else:
@@ -145,7 +191,7 @@ def editor_config(_env_mode):
# ssh+sshync configuration
def ssh_config():
def ssh_config(_reconfig=False):
# private key selection/generation
_keys = []
# ensure ~/.ssh directory exists
@@ -155,18 +201,24 @@ def ssh_config():
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)'])
# append a back button if launched optionally
if _reconfig:
_keys.append('BACK')
_key_selected_num = curses_radio(_keys, 'which private ssh key would you like to use for sshyp?')
_gen_index = len(_keys)-2
if _reconfig and _key_selected_num == len(_keys) - 1:
return
_gen_index = len(_keys) - 2
if _key_selected_num >= _gen_index:
_ssh_key = expanduser(curses_text('enter the location for your private ssh key:\n\n\n\n\n(ctrl+g/enter to '
'confirm)\n\nexample input:\n\n~/.ssh/privkey'))
if _key_selected_num == _gen_index:
_passphrase = curses_text('enter your desired ssh keyfile passphrase:\n\n\n\n\n(ctrl+g/enter to confirm)'
'\n\ntip: you can leave this blank to use the keyfile without a passphrase')
'\n\nnote: it is recommended to leave this blank and to use the keyfile without '
'a passphrase (the keyfile itself serves as your identity)')
run(('ssh-keygen', '-q', '-t', 'ed25519', '-N', _passphrase, '-f', _ssh_key))
else:
_ssh_key = _keys[_key_selected_num]
# ssh+sshync configuration
_uiport = curses_text('enter the username, ip, and ssh port of your sshyp server:\n\n\n\n\n(ctrl+g/enter to '
'confirm)\n\nexample inputs:\n\n ipv4: user@10.10.10.10:22\n ipv6: user@[2000:2000:2000:2000'
@@ -188,19 +240,41 @@ def ssh_config():
# device id configuration
def dev_id_config(_ip, _username_ssh, _port, _identity):
def dev_id_config(_port, _username_ssh, _ip, _identity, _reconfig=False):
if _reconfig:
_sure = curses_radio(('no', 'yes'), "WARNING: ensure this sshyp client is synchronized (up-to-date) before "
"changing the device id\n\nfailure to do so may result in sync"
"hronization issues\n\nare you sure you wish to change the device id?")
if _sure != 1:
return
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 '
_device_id_prefix = curses_text('set this device\'s id:\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 '
'database syncing and quick-unlock permissions\n')
'database synchronization and quick-unlock permissions\n')
_device_id_suffix = string_gen('f', randint(24, 48))
_device_id = _device_id_prefix + '-' + _device_id_suffix
# remove existing device ids
for _id in listdir(f"{home}/.config/sshyp/devices"):
_device_id_list = listdir(f"{home}/.config/sshyp/devices")
for _id in _device_id_list:
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, _identity, sshyp_data)
# test server connection and attempt to register device id - only run if _reconfig is True, since the keyfile
# needs to be registered with the server before a successful connection can be made
if _reconfig:
# copy_id_check() returns false if successful
if not copy_id_check(_port, _username_ssh, _ip, _device_id, _identity, sshyp_data):
# remove old device id from registered pool and whitelist
run(('ssh', '-o', 'ConnectTimeout=3', '-i', _identity, '-p', _port, f"{_username_ssh}@{_ip}",
'python3 -c \'from pathlib import Path; '
f'Path("/home/{_username_ssh}/.config/sshyp/devices/{_device_id_list[0]}").unlink(missing_ok=True); '
f'Path("/home/{_username_ssh}/.config/sshyp/whitelist/{_device_id_list[0]}")'
f'.unlink(missing_ok=True)\''), stderr=DEVNULL, stdout=DEVNULL)
else:
sshyp_data.set('CLIENT-ONLINE', 'ssh_error', 'true')
write_config()
curses_radio(['okay'], 'this device will be registered with the server upon the first successful sync\n\n'
'you can force this now by running "sshyp sync"\n\nif you have not done so already, '
'ensure your ssh pubkey has been appended to the server\'s authorized_keys file!')
# quick-unlock configuration
@@ -232,11 +306,11 @@ def refresh_encryption():
_directory = f"{home}/.local/share/sshyp"
# warn the user of potential data loss and prompt to continue
_proceed = curses_radio(('no', 'yes'), "WARNING: proceeding with this action will remove/overwrite any directories"
f" matching the following:\n\n{home}/.local/share/sshyp.old\n{home}/.local/"
"share/sshyp.new\n\nare you sure you wish to re-encrypt all entries?")
if _proceed != 1:
return 3
_sure = curses_radio(('no', 'yes'), "WARNING: proceeding with this action will remove/overwrite any directories"
f" matching the following:\n\n{home}/.local/share/sshyp.old\n{home}/.local/"
"share/sshyp.new\n\nare you sure you wish to re-encrypt all entries?")
if _sure != 1:
return 3
# set new gpg key
gpg_config()
@@ -248,14 +322,14 @@ def refresh_encryption():
# prompt for unlock and display do not close warning
decrypt(None)
curses_radio(['okay'], 'entry optimization may take some time - select "okay" to start - '
'do not terminate this process!')
curses_radio(['okay'], 'entry optimization may take some time (especially on slower devices)\n\nselect "okay" '
'to start\n\ndo not terminate this process!')
# remove existing conflicts
for _extension in ('.new', '.old'):
if exists(f"{_directory}{_extension}"):
rmtree(f"{_directory}{_extension}")
# decrypt, optimize, and re-encrypt each entry with the newly selected key
if isdir(_directory):
_gpg_id = sshyp_data.get('CLIENT-GENERAL', 'gpg_id')
@@ -265,30 +339,52 @@ def refresh_encryption():
encrypt(decrypt(f"{_root}/{_filename[:-4]}"),
f"{_root.replace(_directory, _directory + '.new', 1)}/{_filename[:-4]}", _gpg_id)
# create a backup of the original version and activate the new version
# create a backup of the original version and activate the new version
move(_directory, _directory + '.old')
move(_directory + '.new', _directory)
return 1
else:
return 2
# PORT START WHITELIST-SERVER
# removes a registered device id from the server-side pool and prunes the quick-unlock whitelist
def registered_dev_id_remover(_back=False):
_device_ids = listdir(f"{home}/.config/sshyp/devices") + ['BACK']
_whitelisted_ids = listdir(f"{home}/.config/sshyp/whitelist")
while not _back:
_del_id = curses_radio(_device_ids, 'WARNING: only remove registered device ids if they are no longer in use'
'\n\nthe removal of an active device id will result in the malfunction of '
'the device still using the removed id\n\nregistered device id to remove:')
if _del_id == len(_device_ids) - 1:
_back = True
else:
# remove deleted device id from whitelist
if _device_ids[_del_id] in _whitelisted_ids:
remove(f"{home}/.config/sshyp/whitelist/{_whitelisted_ids[_del_id]}")
_whitelisted_ids = [_id for _id in _whitelisted_ids if _id != _device_ids[_del_id]]
# remove deleted device id from device pool
remove(f"{home}/.config/sshyp/devices/{_device_ids[_del_id]}")
del _device_ids[_del_id]
# takes input from the user to set up quick-unlock pin
def whitelist_setup():
_gpg_password_temp = str(curses_text('full gpg passphrase:\n\n\n\n\n(ctrl+g/enter to confirm)'))
_half_length = int(len(_gpg_password_temp)/2)
_half_length = len(_gpg_password_temp) // 2
try:
_short_password_length = int(curses_text(f"quick unlock pin length ({_half_length}):\n\n\n\n\n(ctrl+g/enter "
"to confirm)\n\npin must be half the length of the gpg passphrase "
"or less\n\ncannot be a negative number"))
if not 0 <= _short_password_length <= _half_length:
"to confirm)\n\nnote: do NOT enter your desired pin - this is simply "
"an integer used to determine the length of the auto-generated pin"
"\n\npin must be half the length of the gpg passphrase or less and "
"must be greater than 0"))
if not 0 < _short_password_length <= _half_length:
_short_password_length = _half_length
except ValueError:
_short_password_length = _half_length
_i, _quick_unlock_password, _quick_unlock_password_excluded = 0, '', ''
for _char in _gpg_password_temp:
if _i % 2 == 1 and _i < _short_password_length*2:
if _i % 2 == 1 and _i < _short_password_length * 2:
_quick_unlock_password += _char
else:
_quick_unlock_password_excluded += _char
@@ -298,6 +394,8 @@ def whitelist_setup():
open(f"{home}/.config/sshyp/gpg-gen", 'w').writelines([
'Key-Type: 1\n', 'Key-Length: 4096\n', 'Key-Usage: sign encrypt\n', 'Name-Real: sshyp\n',
'Name-Comment: gpg-sshyp-whitelist\n', 'Name-Email: github.com/rwinkhart/sshyp\n', 'Expire-Date: 0'])
curses_radio(['okay'], 'gpg key generation may take some time (especially on slower devices)\n\nselect "okay" '
'to start\n\ndo not terminate this process!')
run(('gpg', '-q', '--pinentry-mode', 'loopback', '--batch', '--generate-key', '--passphrase',
_quick_unlock_password, f"{home}/.config/sshyp/gpg-gen"))
remove(f"{home}/.config/sshyp/gpg-gen")
@@ -305,43 +403,44 @@ def whitelist_setup():
# encrypt excluded with the assembly key
from sshyp import encrypt
encrypt(_quick_unlock_password_excluded, f"{home}/.config/sshyp/excluded", _gpg_id)
encrypt([_quick_unlock_password_excluded], f"{home}/.config/sshyp/excluded", _gpg_id)
curses_radio(['okay, I have it memorized'], f"your quick-unlock pin: {_quick_unlock_password}")
# adds or removes quick-unlock whitelisted device ids
def whitelist_manage(_action):
_whitelisted_ids = listdir(f"{home}/.config/sshyp/whitelist")
def whitelist_manage(_action, _back=False):
_whitelisted_ids = listdir(f"{home}/.config/sshyp/whitelist") + ['BACK']
_device_ids = listdir(f"{home}/.config/sshyp/devices")
# a value of True indicates adding
if _action:
_unwhitelisted_ids = []
for _id in _device_ids:
if _id not in _whitelisted_ids:
_unwhitelisted_ids.append(_id)
_unwhitelisted_ids.append('cancel')
_add_id = curses_radio(_unwhitelisted_ids, 'id to add to whitelist:')
if _add_id == len(_unwhitelisted_ids)-1:
return
open(f"{home}/.config/sshyp/whitelist/{_unwhitelisted_ids[_add_id]}", 'w').write('')
else:
_whitelisted_choices = _whitelisted_ids + ['cancel']
_del_id = curses_radio(_whitelisted_choices, 'id to remove from whitelist:')
if _del_id == len(_whitelisted_choices)-1:
return
remove(f"{home}/.config/sshyp/whitelist/{_whitelisted_ids[_del_id]}")
_unwhitelisted_ids = []
for _id in _device_ids:
if _id not in _whitelisted_ids:
_unwhitelisted_ids.append(_id)
_unwhitelisted_ids.append('BACK')
# prune deleted device ids from whitelist
for _id in _whitelisted_ids:
if _id not in _device_ids:
remove(f"{home}/.config/sshyp/whitelist/{_id}")
while not _back:
# _action == True indicates adding
if _action:
_add_id = curses_radio(_unwhitelisted_ids, 'id to add to whitelist:')
if _add_id == len(_unwhitelisted_ids) - 1:
_back = True
else:
open(f"{home}/.config/sshyp/whitelist/{_unwhitelisted_ids[_add_id]}", 'w').write('')
del _unwhitelisted_ids[_add_id]
# _action == False indicates removing
else:
_del_id = curses_radio(_whitelisted_ids, 'id to remove from whitelist:')
if _del_id == len(_whitelisted_ids) - 1:
_back = True
else:
remove(f"{home}/.config/sshyp/whitelist/{_whitelisted_ids[_del_id]}")
del _whitelisted_ids[_del_id]
# runs quick-unlock configuration menu
def whitelist_menu():
while True:
_choice = curses_radio(('setup/create pin', 'add to whitelist', 'remove from whitelist',
_choice = curses_radio(('setup/create pin', 'add to whitelist', 'remove from whitelist',
'BACK'), 'quick-unlock/whitelist management')
if _choice == 0:
whitelist_setup()
@@ -360,23 +459,27 @@ 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.1").read()
# the version listed below will NOT always match the version of sshyp being used
# it is only updated if new extensions are incompatible with previous sshyp versions
_file_data = urlopen("https://raw.githubusercontent.com/rwinkhart/sshyp-labs/main/pointers/v1.5.2").read()
_pointer = ConfigParser(interpolation=None)
_pointer.read_string(_file_data.decode('utf-8'))
_extensions = _pointer.sections()
_extensions.append('CANCEL')
_extensions.append('BACK')
_choice = curses_radio(_extensions, 'select an extension for more info')
if _choice == len(_extensions)-1:
if _choice == len(_extensions) - 1:
return False
_selected = _extensions[_choice]
_choice = curses_radio(('no', 'yes'), f"description: {_pointer.get(_selected, 'desc')}\n\nusage: "
f"{_pointer.get(_selected, 'usage')}\n\ninstall {_selected}?")
_divider = (stdscr.getmaxyx()[1]) * '-'
_choice = curses_radio(('no', 'yes'), '# description\n' + _divider + '\n\n' + _pointer.get(_selected, 'desc') +
'\n\n# usage\n' + _divider + '\n\n' + _pointer.get(_selected, 'usage').replace('<br>', '\n')
+ '\n\n' + _divider + '\n\ninstall ' + _selected + '?')
# if installing the extension...
if _choice == 1:
# download extension files to temporary directory
_exe_dir, _ini_dir = f"{gettempdir()}/sshyp_exe", f"{gettempdir()}/sshyp_ini"
_ext_exe = urlretrieve(_pointer.get(_selected, 'exe'), _exe_dir)
_ext_ini = urlretrieve(_pointer.get(_selected, 'ini'), _ini_dir)
urlretrieve(_pointer.get(_selected, 'exe'), _exe_dir)
urlretrieve(_pointer.get(_selected, 'ini'), _ini_dir)
# set permissions under active user
chmod(_exe_dir, 0o755)
chmod(_ini_dir, 0o644)
@@ -389,9 +492,9 @@ def extension_remover():
_installed = []
for _extension in listdir('/usr/lib/sshyp/extensions'):
_installed.append(_extension[:-4])
_installed.append('CANCEL')
_installed.append('BACK')
_choice = curses_radio(_installed, 'select an extension to uninstall')
if _choice == len(_installed)-1:
if _choice == len(_installed) - 1:
return False
_sure = curses_radio(('no', 'yes'), f"are you sure you want to remove {_installed[_choice]}?")
if _sure == 0:
@@ -438,14 +541,14 @@ def global_menu(_scr, _device_type, _top_message):
while True:
_options, _choice, _exit_signal = ['change device/synchronization types'], 0, False
if _device_type == 'client':
_options.extend(['change gpg key', 're-configure ssh(ync)', 'change device name',
_options.extend(['change gpg key', 're-configure ssh(ync)', 'change device id',
'[OPTIONAL, RECOMMENDED] set custom text editor',
'[OPTIONAL] enable/disable quick-unlock',
'[OPTIONAL] re-encrypt/optimize entries',
'[OPTIONAL] extension management'])
else:
_options.extend(['manage quick-unlock/whitelist'])
_options.extend(['EXIT/DONE'])
_options.extend(['remove registered device ids', 'manage quick-unlock/whitelist'])
_options.append('EXIT/DONE')
_choice += curses_radio(_options, _top_message)
if _choice == 0:
@@ -458,7 +561,7 @@ def global_menu(_scr, _device_type, _top_message):
# ...and text editor settings are missing
if not sshyp_data.has_option('CLIENT-GENERAL', 'text_editor'):
editor_config(True)
# ...and online (synced) mode is enabled...
# ...and online (synchronized) mode is enabled...
if _dev_sync_types[1] == 'false':
# ...and quick-unlock settings are missing
if not sshyp_data.has_option('CLIENT-ONLINE', 'quick_unlock_enabled'):
@@ -472,27 +575,30 @@ def global_menu(_scr, _device_type, _top_message):
if not listdir(f"{home}/.config/sshyp/devices"):
if None in (_ip, _username_ssh, _port):
_ip, _username_ssh, _port, _identity = ssh_config()
dev_id_config(_ip, _username_ssh, _port, _identity)
dev_id_config(_port, _username_ssh, _ip, _identity)
# ...or ssh_error is missing
elif not sshyp_data.has_option('CLIENT-ONLINE', 'ssh_error'):
sshyp_data.set('CLIENT-ONLINE', 'ssh_error', '1')
sshyp_data.set('CLIENT-ONLINE', 'ssh_error', '1')
write_config()
_device_type = _dev_sync_types[0]
elif _choice == 1:
if _device_type == 'client':
gpg_config()
else:
whitelist_menu()
registered_dev_id_remover()
elif _choice == 2:
if _device_type == 'client':
ssh_config()
ssh_config(True)
else:
whitelist_menu()
elif _choice == 3:
if _device_type == 'client':
if not sshyp_data.has_section('SSHYNC'):
ssh_config()
dev_id_config(sshyp_data.get('SSHYNC', 'port'), sshyp_data.get('SSHYNC', 'user'),
sshyp_data.get('SSHYNC', 'ip'), sshyp_data.get('SSHYNC', 'identity_file'), True)
else:
_exit_signal = True
elif _choice == 3:
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', 'identity_file'))
elif _choice == 4:
editor_config(False)
elif _choice == 5:
@@ -553,12 +659,11 @@ def initial_setup(_scr):
# online (synchronized mode) configuration
if _dev_sync_types[1] != 'true':
# ssh+sshync configuration
_ip, _username_ssh, _port, _identity = ssh_config()
# device id configuration
dev_id_config(_ip, _username_ssh, _port, _identity)
dev_id_config(_port, _username_ssh, _ip, _identity)
# PORT START CLIPTOOL
# check for clipboard tool and display warning if missing
@@ -578,39 +683,52 @@ def initial_setup(_scr):
# 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:')
# set gm_device_type so that after the init menu is terminated the global menu knows the device type
global gm_device_type
gm_device_type = _dev_sync_types[0]
return
# runs the specified entry function (menu start point) within a curses wrapper
def wrapped_entry(_gm_device_type, _gm_top_message='configuration options:'):
from curses import wrapper, use_default_colors
global gm_device_type
gm_device_type = _gm_device_type
# a boolean value represents init
if isinstance(_gm_device_type, bool):
if isinstance(gm_device_type, bool):
wrapper(lambda _wrap_stdscr: (use_default_colors(), initial_setup(_wrap_stdscr)))
# any other value will be provided as the global menu device type
else:
_repeat = True
while _repeat:
try:
_ext_name, _escalator, _action = \
wrapper(lambda _wrap_stdscr: (use_default_colors(),
global_menu(_wrap_stdscr, _gm_device_type, _gm_top_message)))[1]
except ChildProcessError:
print("\n\u001b[38;5;9merror: privilege escalation required\n\nneither 'doas' nor 'sudo' were found in "
"the system's $PATH\u001b[0m\n")
return
# only run if privilege escalation is needed
if _action is not None:
if _action:
# install with privilege escalation (outside of curses)
from tempfile import gettempdir
_exe_dir, _ini_dir = f"{gettempdir()}/sshyp_exe", f"{gettempdir()}/sshyp_ini"
run((_escalator, 'chown', 'root:root', _exe_dir, _ini_dir))
run((_escalator, 'mv', _exe_dir, f"/usr/lib/sshyp/{_ext_name}"))
run((_escalator, 'mv', _ini_dir, f"/usr/lib/sshyp/extensions/{_ext_name}.ini"))
# any other value will be interpreted as the global menu device type
# this code still runs when called for init once the init menu terminates
_repeat = True
while _repeat:
try:
_ext_name, _escalator, _action = \
wrapper(lambda _wrap_stdscr: (use_default_colors(),
global_menu(_wrap_stdscr, gm_device_type, _gm_top_message)))[1]
except ChildProcessError:
print("\n\u001b[38;5;9merror: privilege escalation required\n\nneither 'doas' nor 'sudo' were found in "
"the system's $PATH\u001b[0m\n")
return
# only run if privilege escalation is needed
if _action is not None:
if _action:
# install with privilege escalation (outside of curses)
from tempfile import gettempdir
_exe_dir, _ini_dir = f"{gettempdir()}/sshyp_exe", f"{gettempdir()}/sshyp_ini"
# PORT START TWEAK-EXT-CHOWN
if uname()[0] == 'FreeBSD':
run((_escalator, 'chown', 'root:wheel', _exe_dir, _ini_dir))
else:
# uninstall with privilege escalation (outside of curses)
run((_escalator, 'rm', '-I', f"/usr/lib/sshyp/{_ext_name}",
f"/usr/lib/sshyp/extensions/{_ext_name}.ini"))
run((_escalator, 'chown', 'root:root', _exe_dir, _ini_dir))
# PORT END TWEAK-EXT-CHOWN
run((_escalator, 'mv', _exe_dir, f"/usr/lib/sshyp/{_ext_name}"))
run((_escalator, 'mv', _ini_dir, f"/usr/lib/sshyp/extensions/{_ext_name}.ini"))
else:
_repeat = False
# uninstall with privilege escalation (outside of curses)
run((_escalator, 'rm', f"/usr/lib/sshyp/{_ext_name}",
f"/usr/lib/sshyp/extensions/{_ext_name}.ini"))
else:
_repeat = False
+10 -2
View File
@@ -1,6 +1,6 @@
#!/bin/sh
version=$(head -n1 extra/changelog-archive/2023 | cut -c8-)
version=$(head -n1 extra/changelog-archive/2024 | cut -c8-)
if [ -z "$2" ]; then
revision=1
else
@@ -21,6 +21,7 @@ _create_generic_linux() {
cd port-jobs
./CLIPTOOL.py LINUX
./CLIPBOARD.py LINUX
./CHOWN.py
./UNAME.py LINUX
./COMMENTS.py ALL
./BLANKS.py
@@ -135,7 +136,7 @@ licenses {
\"GNU GPL v3\"
}
copyrights {
\"2021-2023 Randall Winkhart\"
\"2021-2024 Randall Winkhart\"
}
provides {
sshyp_client = "$version"
@@ -158,6 +159,7 @@ urls {
./RMEXTMAN.py
./CLIPTOOL.py
./CLIPBOARD.py HAIKU
./CHOWN.py
./UNAME.py TMP
./COMMENTS.py ALL
./BLANKS.py
@@ -200,6 +202,7 @@ _create_deb() {
./CLIPBOARD.py WSL
special=WSL-ONLY-DEBIAN
fi
./CHOWN.py
./UNAME.py LINUX
./COMMENTS.py ALL
./BLANKS.py
@@ -253,6 +256,7 @@ _create_termux() {
./RMEXTMAN.py
./CLIPTOOL.py
./CLIPBOARD.py TERMUX
./CHOWN.py
./UNAME.py TERMUX
./COMMENTS.py ALL
./BLANKS.py
@@ -313,6 +317,7 @@ cp -r %%{_sourcedir}/usr %%{buildroot}
/usr/lib/sshyp/sshyp.py
/usr/lib/sshyp/sshync.py
/usr/lib/sshyp/stweak.py
/usr/lib/sshyp/clipclear.py
/usr/lib/sshyp/extensions/
/usr/share/bash-completion/completions/sshyp
/usr/share/zsh/site-functions/_sshyp
@@ -325,6 +330,7 @@ cp -r %%{_sourcedir}/usr %%{buildroot}
cd port-jobs
./CLIPTOOL.py LINUX
./CLIPBOARD.py LINUX
./CHOWN.py
./UNAME.py LINUX
./COMMENTS.py ALL
./BLANKS.py
@@ -377,6 +383,7 @@ printf "/usr/bin/sshyp
/usr/lib/sshyp/sshyp.py
/usr/lib/sshyp/sshync.py
/usr/lib/sshyp/stweak.py
/usr/lib/sshyp/clipclear.py
/usr/local/share/bash-completion/completions/sshyp
/usr/local/share/zsh/site-functions/_sshyp
/usr/share/licenses/sshyp/license
@@ -388,6 +395,7 @@ printf "/usr/bin/sshyp
cd port-jobs
./CLIPTOOL.py LINUX
./CLIPBOARD.py BSD
./CHOWN.py BSD
./UNAME.py TMP
./COMMENTS.py ALL
./BLANKS.py
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env python3
import re
from sys import argv
# read arguments
arguments = argv[1:]
# define PORT target
string1 = '# PORT START TWEAK-EXT-CHOWN'
string2 = '# PORT END TWEAK-EXT-CHOWN'
# define replacement text depending on arguments
if len(arguments) > 0 and arguments[0] == 'BSD':
replacement = "run((_escalator, 'chown', 'root:wheel', _exe_dir, _ini_dir))"
else:
replacement = "run((_escalator, 'chown', 'root:root', _exe_dir, _ini_dir))"
# read input file
text = open('working/stweak.py', 'r').read()
# compile regex and modify text
regex = re.compile(f"{string1}.*?{string2}", re.DOTALL)
# find and replace the defined PORT target
new_text = re.sub(regex, replacement, text)
# write updated text
open('working/stweak.py', 'w').write(new_text)
+1 -1
View File
@@ -39,7 +39,7 @@ if argv[1] == hash_paste.hexdigest():
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())
hash_paste.update(run('wl-paste', stdout=PIPE, stderr=DEVNULL).stdout.strip())
if argv[1] == hash_paste.hexdigest():
run(('wl-copy', '-c'))
else:
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
import re
targets = (('TWEAK-EXTEND-FUNCTIONS', 'stweak.py', '\n', '\n\n', ''),
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 "
+1 -1
View File
@@ -1,5 +1,5 @@
sshyp is a FOSS password manager that uses an sftp-based syncing back-end.
Copyright (C) 2021-2023 Randall Winkhart idgr@tutanota.com
Copyright (C) 2021-2024 Randall Winkhart idgr@tutanota.com
This program is free software: you can redistribute it and/or modify
it under the terms of version 3 (only) of the GNU General Public License