From 18b27c9dcebd509abc200d4bc43766716667cf5d Mon Sep 17 00:00:00 2001 From: Cuan Date: Tue, 12 Oct 2021 23:13:03 -0400 Subject: [PATCH] Partial fix for data loss (syncing temporarily disabled) --- bin/rpass | 67 +++++++++++++++++++------------------ bin/sshync.py | 70 +++++++++++++++++++++++++++++++++++++++ share/doc/rpass/changelog | 25 +++++++++----- 3 files changed, 120 insertions(+), 42 deletions(-) create mode 100755 bin/sshync.py diff --git a/bin/rpass b/bin/rpass index b7c157a..166dcce 100755 --- a/bin/rpass +++ b/bin/rpass @@ -1,8 +1,9 @@ #!/usr/bin/python3 -# internal modules +# external modules -from os import system +import sshync +from os import system as term from os import remove from os import environ from shutil import move @@ -14,16 +15,12 @@ import random import string -# custom modules +# internal modules def clear(): print('\n' * 100) -def term(cmd): - _ = system(cmd) - - def string_found(string1, string2): if search(r"\b" + escape(string1) + r"\b", string2): return True @@ -116,7 +113,7 @@ if argument == 'version' or argument == '-v': print('/ /') print('/ rpass Copyright (C) 2021 Randall Winkhart /') print('/ /') - print('/ Version 2021.10.12.mr5.1 /') + print('/ Version 2021.10.13.mr5.1 /') print('/ The Notetaker Update /') print('/ /') print('////////////////////////////////////////////////////////\n') @@ -141,17 +138,9 @@ if argument == 'show c': if argument == 'config': try: print() - directorychoice = str(input('Would you like to use the default entry directory (~/.rpass-store/)? (Y/n) ')) - if directorychoice == 'n' or directorychoice == 'N': - print('\nFormat: /this/path/ends/with/a/slash/\n') - directory = str(input('Please input a custom directory: ')) - open("/var/lib/rpass/rpass-dir", 'w').write(directory + '\n') - else: - open("/var/lib/rpass/rpass-dir", 'w').write('/home/' + environ.get('USER') + '/.rpass-store/' + '\n') - print() devicetype = str(input('Will this be a client, server, or offline device? (C/s/o) ')) if devicetype == 's' or devicetype == 'S': - open("/var/lib/rpass/rpass-ssh", 'w').write('\n\n\n\n' + devicetype) + open("/var/lib/rpass/rpass-device", 'w').write(devicetype) print('\nMake sure the ssh service is running and properly configured.\n') print('Configuration complete!\n') else: @@ -182,7 +171,7 @@ if argument == 'config': term('gpg -r ' + str(gpgid) + ' -e ' + '/var/lib/rpass/lock') remove('/var/lib/rpass/lock') if devicetype == 'o' or devicetype == 'O': - open("/var/lib/rpass/rpass-ssh", 'w').write('\n\n\n\n' + devicetype) + open("/var/lib/rpass/rpass-device", 'w').write(devicetype) print('\nConfiguration complete!\n') elif devicetype != 's' and devicetype != 'S': devicetype = 'c' @@ -203,8 +192,11 @@ if argument == 'config': directoryssh = str(input('What directory is the server using?: ')) else: directoryssh = ('/home/' + usernamessh + '/.rpass-store/') - open("/var/lib/rpass/rpass-ssh", 'w').write(usernamessh + '\n' + ip + '\n' + port + '\n' + directoryssh + - '\n' + devicetype) + open("/var/lib/rpass/rpass-device", 'w').write(devicetype) + sshync.make_sentinel_profile('/var/lib/rpass/rpass.sshync', '/home/' + environ.get('USER') + + '/.rpass-store/', directoryssh, '/home/' + environ.get('USER') + + '/.ssh/rpass', ip, port, usernamessh, '/var/lib/rpass/mod_time', + '/home/nixon/mod_time') print('\nConfiguration complete!\n') except KeyboardInterrupt: print('\n\nConfiguration cancelled.\n') @@ -220,23 +212,20 @@ if argument == '': # import userdata try: - sshinfo = open("/var/lib/rpass/rpass-ssh").readlines() + devicetype = open('/var/lib/rpass/rpass-device').read().strip() + gpgid = open("/var/lib/rpass/rpass-gpg").read().strip() + sshinfo = sshync.get_profile('/var/lib/rpass/rpass.sshync') usernamessh = sshinfo[0].replace('\n', '') ip = sshinfo[1].replace('\n', '') port = sshinfo[2].replace('\n', '') - directoryssh = sshinfo[3].replace('\n', '') - devicetype = sshinfo[4].replace('\n', '') -except (FileNotFoundError, IndexError): - usernamessh, ip, port, directoryssh, devicetype = (0, 0, 0, 0, 0) -directory = '' -try: - gpgid = open("/var/lib/rpass/rpass-gpg").read().strip() - directory = open("/var/lib/rpass/rpass-dir").read().strip() + directory = str(sshinfo[3].replace('\n', '')) + directoryssh = sshinfo[4].replace('\n', '') term('mkdir -p ' + directory) except FileNotFoundError: print('\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!') print("Not all necessary configuration files are present. Please run 'rpass config'!") print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n') + devicetype, sshinfo, directory, directoryssh, ip, port, usernamessh = [0, 0, 0, 0, 0, 0, 0] exit() # no argument @@ -305,6 +294,7 @@ if argument == 'add note' or argument == 'add -n': print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n') rmtree('/dev/shm/' + shmfolder) exit() + open("/var/lib/rpass/mod_time", 'w').write('add') if argument == 'add password' or argument == 'add -p': try: @@ -348,10 +338,12 @@ if argument == 'add password' or argument == 'add -p': print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n') rmtree('/dev/shm/' + shmfolder) exit() + open("/var/lib/rpass/mod_time", 'w').write('add') if argument == 'add folder' or argument == 'add -f': newfolder = str(input('Name of new folder: ')) term('mkdir ' + "'" + directory + newfolder + "'") + open("/var/lib/rpass/mod_time", 'w').write('add') # edit @@ -389,6 +381,7 @@ if argument == 'edit rename' or argument == 'edit relocate' or argument == 'edit print('The file you requested to edit does not exist!') print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n') exit() + open("/var/lib/rpass/mod_time", 'w').write('edit') if argument == 'edit username' or argument == 'edit -u': try: @@ -431,6 +424,7 @@ if argument == 'edit username' or argument == 'edit -u': print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n') rmtree('/dev/shm/' + shmfolder) exit() + open("/var/lib/rpass/mod_time", 'w').write('edit') if argument == 'edit password' or argument == 'edit -p': try: @@ -473,6 +467,7 @@ if argument == 'edit password' or argument == 'edit -p': print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n') rmtree('/dev/shm/' + shmfolder) exit() + open("/var/lib/rpass/mod_time", 'w').write('edit') if argument == 'edit url' or argument == 'edit -l': try: @@ -515,6 +510,7 @@ if argument == 'edit url' or argument == 'edit -l': print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n') rmtree('/dev/shm/' + shmfolder) exit() + open("/var/lib/rpass/mod_time", 'w').write('edit') if argument == 'edit note' or argument == 'edit -n': try: @@ -556,6 +552,7 @@ if argument == 'edit note' or argument == 'edit -n': print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n') rmtree('/dev/shm/' + shmfolder) exit() + open("/var/lib/rpass/mod_time", 'w').write('edit') # copy @@ -593,6 +590,7 @@ if argument == 'copy username' or argument == 'copy -u': print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n') rmtree('/dev/shm/' + shmfolder) exit() + open("/var/lib/rpass/mod_time", 'w').write('copy') if argument == 'copy password' or argument == 'copy -p': try: @@ -619,6 +617,7 @@ if argument == 'copy password' or argument == 'copy -p': print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n') rmtree('/dev/shm/' + shmfolder) exit() + open("/var/lib/rpass/mod_time", 'w').write('copy') if argument == 'copy url' or argument == 'copy -l': try: @@ -645,6 +644,7 @@ if argument == 'copy url' or argument == 'copy -l': print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n') rmtree('/dev/shm/' + shmfolder) exit() + open("/var/lib/rpass/mod_time", 'w').write('copy') if argument == 'copy note' or argument == 'copy -n': try: @@ -671,6 +671,7 @@ if argument == 'copy note' or argument == 'copy -n': print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n') rmtree('/dev/shm/' + shmfolder) exit() + open("/var/lib/rpass/mod_time", 'w').write('copy') # gen @@ -717,6 +718,7 @@ if argument == 'gen': print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n') rmtree('/dev/shm/' + shmfolder) exit() + open("/var/lib/rpass/mod_time", 'w').write('gen') if argument == 'gen update' or argument == 'gen -u': try: @@ -765,6 +767,7 @@ if argument == 'gen update' or argument == 'gen -u': print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n') rmtree('/dev/shm/' + shmfolder) exit() + open("/var/lib/rpass/mod_time", 'w').write('gen') # remove @@ -805,6 +808,7 @@ if argument == 'remove' or argument == '-rm': print('The file you requested to delete does not exist!') print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n') exit() + open("/var/lib/rpass/mod_time", 'w').write('rm') if argument.__contains__('-rm') or string_found('remove', argument) or string_found('add', argument) or \ string_found('edit', argument) or string_found('gen', argument) or string_found('copy', argument) or \ @@ -828,7 +832,4 @@ if devicetype == 'c': if argument.__contains__('-rm') or string_found('remove', argument) or string_found('add', argument) or \ string_found('edit', argument) or string_found('gen', argument) or argument == 'sync' or argument == '-s': print('Syncing entries with other device(s)...\n') - term('rsync -v -H -r -l -t -p -e ' + '"ssh -i ~/.ssh/rpass -p ' + port + '" ' + directory + ' ' + - usernamessh + '@' + ip + ':' + directoryssh) - term('rsync -v -H -r -l -t -p --delete -e ' + '"ssh -i ~/.ssh/rpass -p ' + port + '" ' + usernamessh - + '@' + ip + ':' + directoryssh + ' ' + directory) + sshync.run_profile('/var/lib/rpass/rpass.sshync') diff --git a/bin/sshync.py b/bin/sshync.py new file mode 100755 index 0000000..f1f0621 --- /dev/null +++ b/bin/sshync.py @@ -0,0 +1,70 @@ +#!/usr/bin/python3 + +# sshync 2021.10.12.unreleased1 + +# sshync is a wrapper around sftp that functions as a Python library alternative to scp +# openssh (on POSIX compliant Unix) is required + +# sshync will always overwrite the older files with the newer files, regardless of all other conditions +# ^the age is determined by the date and time of the last modification to the files + +# syncing via sshync is always done recursively + +# external modules + +from os.path import getmtime +from subprocess import getoutput as term_output + + +# internal modules + + +def make_profile(profile_dir, local_dir, remote_dir, identity, ip, port, user): + open(profile_dir, 'w').write(user + '\n' + ip + '\n' + port + '\n' + local_dir + '\n' + remote_dir + '\n' + identity + + '\n\n\n') + + +def make_sentinel_profile(profile_dir, local_dir, remote_dir, identity, ip, port, user, local_sentinel, rem_sentinel): + open(profile_dir, 'w').write(user + '\n' + ip + '\n' + port + '\n' + local_dir + '\n' + remote_dir + '\n' + identity + + '\n' + local_sentinel + '\n' + rem_sentinel + '\n') + + +def get_profile(profile_dir): + try: + profile_data = open(profile_dir).readlines() + except (FileNotFoundError, IndexError): + profile_data = 0 + return profile_data + + +def run_profile(profile_dir): + # import profile data + profile_data = get_profile(profile_dir) + user = profile_data[0].replace('\n', '') + ip = profile_data[1].replace('\n', '') + port = profile_data[2].replace('\n', '') + local_dir = profile_data[3].replace('\n', '') + remote_dir = profile_data[4].replace('\n', '') + identity = profile_data[5].replace('\n', '') + local_sentinel = profile_data[6].replace('\n', '') + rem_sentinel = profile_data[7].replace('\n', '') + # get file modification times + if local_sentinel == '': + local_modification_time = getmtime(local_dir) + remote_modification_time = float( + term_output("ssh -i " + "'" + identity + "'" + " -p " + port + " " + user + "@" + + ip + " \"python -c 'from os.path import getmtime; mod_time = " + "(getmtime(" + '\\"' + remote_dir + '\\"' + ")); print(mod_time);" + "'" + "\"")) + else: + local_modification_time = getmtime(local_sentinel) + remote_modification_time = float( + term_output("ssh -i " + "'" + identity + "'" + " -p " + port + " " + user + "@" + + ip + " \"python -c 'from os.path import getmtime; mod_time = " + "(getmtime(" + '\\"' + rem_sentinel + '\\"' + ")); print(mod_time);" + "'" + "\"")) + # compare file modification times + if local_modification_time > remote_modification_time: + print('\nLocal is newer, uploading...\n') # upload + else: + print('\nRemote is newer, downloading...\n') # download diff --git a/share/doc/rpass/changelog b/share/doc/rpass/changelog index 899ea03..d031376 100755 --- a/share/doc/rpass/changelog +++ b/share/doc/rpass/changelog @@ -1,27 +1,33 @@ <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> -rpass 2021.10.12.mr5.1 +rpass 2021.10.13.mr5.1 -The Notetaker Update - Patch 1 +The Notetaker Update - (Mega) Patch 1 -This release fixes major bugs from previous releases and offers new packages of rpass. +This release fixes major bugs from previous releases. +This includes a complete overhaul of the entry syncing system. New features: -**rpass is now packaged for Termux (basic Android support) -**rpass is now packaged for Alpine Linux -**custom SSH keys are now properly supported +- none! Changes: -- fixed an issue with copying notes (currently only copies first line, may be changed in the future) -**fixed an issue with syncing that could cause data loss +- fixed an issue with copying notes (currently only copies first line, will be changed in the future) +** fixed an issue with syncing that could cause data loss ^ This issue has been in rpass (undiagnosed) since rsync support was first introduced - the solution involved dropping rsync and switching to sftp. This change should have no user-facing impact and is overall a net positive, as rsync is no longer a dependency and the data loss bug is now fixed. Time to rename rpass! -- rpass is validated to work with Python 3.10 +- rpass is validated to work with Python 3.10 (no changes were needed) + +Planned for next update (Patch mr5.2) + +**package for Termux (basic Android support) +**package for Alpine Linux +**re-add support for custom directories and custom ssh keys +**adjust copy note feature to copy all lines Planned for next update (The Beauty Update): @@ -36,6 +42,7 @@ Planned for next update (The Optimus Update): - add an entry name masking system - modularize and shrink rpass code as much as possible - shrink rpass package size as much as possible + ^ run question modules in file write commands to return values Planned, no timeline: