diff --git a/core/clipClearProcessGeneric.go b/core/clipClearProcessGeneric.go index 9dcbf00..a9fad78 100644 --- a/core/clipClearProcessGeneric.go +++ b/core/clipClearProcessGeneric.go @@ -26,7 +26,10 @@ func clipClearProcess(assignedContents string) error { // if assignedContents is empty, clear the clipboard immediately and unconditionally if assignedContents == "" { - clearClipboard() + err := clearClipboard() + if err != nil { + return err + } return nil } diff --git a/core/copy.go b/core/copy.go index 515e799..28a0d9d 100644 --- a/core/copy.go +++ b/core/copy.go @@ -52,7 +52,10 @@ func CopyArgument(targetLocation string, field int) error { if err != nil { return err } - copyString(true, token) + err = copyString(true, token) + if err != nil { + return err + } // sleep until next 30-second interval time.Sleep(time.Duration(30-(currentTime.Second()%30)) * time.Second) } @@ -62,18 +65,22 @@ func CopyArgument(targetLocation string, field int) error { } // copy field to clipboard, launch clipboard clearing process - copyString(false, copySubject) + err = copyString(false, copySubject) + if err != nil { + return err + } } return nil } // ClipClearArgument reads the assigned clipboard contents from stdin and passes them to clipClearProcess. -func ClipClearArgument() { +func ClipClearArgument() error { assignedContents := back.ReadFromStdin() if assignedContents == "" { os.Exit(0) // use os.Exit instead of core.Exit, as this function runs out of a background subprocess that is invisible to the user (will never appear in GUI/TUI environment) } - clipClearProcess(assignedContents) + err := clipClearProcess(assignedContents) + return err } // GenTOTP generates a TOTP token from a secret (supports standard and Steam TOTP). diff --git a/core/init.go b/core/init.go index c78e033..04eb03a 100644 --- a/core/init.go +++ b/core/init.go @@ -14,7 +14,7 @@ import ( ) // LibmuttonInit creates the libmutton config structure based on user input. -// rcwPassphrase and clientSpecificIniData are cab be left blank if not needed. +// rcwPassphrase and clientSpecificIniData can be left blank if not needed. func LibmuttonInit(inputCB func(prompt string) string, clientSpecificIniData [][3]string, rcwPassphrase []byte, preserveOldConfigDir bool) error { r := strings.ToLower(inputCB("Configure SSH settings (for synchronization)? (y/N)")) if len(r) > 0 && r[0] == 'y' { @@ -44,7 +44,7 @@ func LibmuttonInit(inputCB func(prompt string) string, clientSpecificIniData [][ } //// write config file //// temporarily assign sshEntryRoot and sshIsWindows to null to pass initial device ID registration - cfg.WriteConfig(append( + err = cfg.WriteConfig(append( clientSpecificIniData, [][3]string{ {"LIBMUTTON", "sshUser", sshUser}, @@ -54,18 +54,30 @@ func LibmuttonInit(inputCB func(prompt string) string, clientSpecificIniData [][ {"LIBMUTTON", "sshKeyProtected", strconv.FormatBool(sshKeyProtected)}, {"LIBMUTTON", "sshEntryRoot", "null"}, {"LIBMUTTON", "sshIsWindows", "false"}}...), nil, false) + if err != nil { + return errors.New("unable to write config file: " + err.Error()) + } // generate and register device ID sshEntryRoot, sshIsWindows, err := synccycles.DeviceIDGen(oldDeviceID) if err != nil { return errors.New("unable to generate device ID: " + err.Error()) } - cfg.WriteConfig([][3]string{{"LIBMUTTON", "sshEntryRoot", sshEntryRoot}, {"LIBMUTTON", "sshIsWindows", sshIsWindows}}, nil, true) + err = cfg.WriteConfig([][3]string{{"LIBMUTTON", "sshEntryRoot", sshEntryRoot}, {"LIBMUTTON", "sshIsWindows", sshIsWindows}}, nil, true) + if err != nil { + return errors.New("unable to write config file: " + err.Error()) + } } else { // initialize libmutton directories - global.DirInit(preserveOldConfigDir) + _, err := global.DirInit(preserveOldConfigDir) + if err != nil { + return errors.New("unable to initialize libmutton directories: " + err.Error()) + } // write config file if len(clientSpecificIniData) > 0 { // TODO test passing empty clientSpecificIniData - cfg.WriteConfig(clientSpecificIniData, nil, false) + err = cfg.WriteConfig(clientSpecificIniData, nil, false) + if err != nil { + return errors.New("unable to write config file: " + err.Error()) + } } } // generate rcw sanity check file (if requested) diff --git a/core/launchClipClearProcessCLIUNIX.go b/core/launchClipClearProcessCLIUNIX.go index 0f9d118..d780186 100644 --- a/core/launchClipClearProcessCLIUNIX.go +++ b/core/launchClipClearProcessCLIUNIX.go @@ -15,6 +15,6 @@ import ( func LaunchClipClearProcess(copySubject string, isWayland bool) { cmd := exec.Command(os.Args[0], "clipclear", strconv.FormatBool(isWayland)) back.WriteToStdin(cmd, copySubject) - cmd.Start() + _ = cmd.Start() os.Exit(0) // use os.Exit directly since this version of this function is only meant for non-interactive CLI implementations } diff --git a/crypt/rcw.go b/crypt/rcw.go index d308d40..f9cddf6 100644 --- a/crypt/rcw.go +++ b/crypt/rcw.go @@ -85,7 +85,7 @@ func launchRCWDProcess() []byte { if Daemonize { cmd := exec.Command(os.Args[0], "startrcwd") back.WriteToStdin(cmd, string(passphrase)) - cmd.Start() + _ = cmd.Start() } return passphrase diff --git a/global/deviceIDs.go b/global/deviceIDs.go index 5ad92d1..83ca4bd 100644 --- a/global/deviceIDs.go +++ b/global/deviceIDs.go @@ -6,7 +6,7 @@ import ( "os" ) -// GetOldDeviceID returns the current device ID or +// GetCurrentDeviceID returns the current device ID or // FSMisc if there is no device ID (e.g. first run). func GetCurrentDeviceID() (string, error) { deviceIDList, err := GenDeviceIDList() diff --git a/libmuttonserver.go b/libmuttonserver.go index 880b4af..3fa4401 100644 --- a/libmuttonserver.go +++ b/libmuttonserver.go @@ -38,23 +38,23 @@ func main() { case "fetch": // print all information needed for syncing to stdout for interpretation by the client // stdin[0] is expected to be the device ID - syncserver.GetRemoteDataFromServer(stdin[0]) + _ = syncserver.GetRemoteDataFromServer(stdin[0]) case "rename": // move an entry to a new location before using fallthrough to add its previous iteration to the deletions directory // stdin[0] is evaluated after fallthrough // stdin[1] is expected to be the OLD incomplete target location with FSPath representing path separators - Always pass in UNIX format // stdin[2] is expected to be the NEW incomplete target location with FSPath representing path separators - Always pass in UNIX format - synccommon.RenameLocal(strings.ReplaceAll(stdin[1], global.FSPath, "/"), strings.ReplaceAll(stdin[2], global.FSPath, "/"), true) + _ = synccommon.RenameLocal(strings.ReplaceAll(stdin[1], global.FSPath, "/"), strings.ReplaceAll(stdin[2], global.FSPath, "/"), true) fallthrough // fallthrough to add the old entry to the deletions directory case "shear": // shear an entry from the server and add it to the deletions directory // stdin[0] is expected to be the device ID // stdin[1] is expected to be the incomplete target location with FSPath representing path separators - Always pass in UNIX format - synccommon.ShearLocal(strings.ReplaceAll(stdin[1], global.FSPath, "/"), stdin[0]) + _, _, _ = synccommon.ShearLocal(strings.ReplaceAll(stdin[1], global.FSPath, "/"), stdin[0]) case "addfolder": // add a new folder to the server // stdin[0] is expected to be the incomplete target location with FSPath representing path separators - Always pass in UNIX format - synccommon.AddFolderLocal(strings.ReplaceAll(stdin[0], global.FSPath, "/")) + _ = synccommon.AddFolderLocal(strings.ReplaceAll(stdin[0], global.FSPath, "/")) case "register": // register a new device ID // stdin[0] is expected to be the device ID @@ -68,7 +68,10 @@ func main() { fmt.Print(global.EntryRoot + global.FSSpace + strconv.FormatBool(global.IsWindows)) case "init": // create the necessary directories for libmuttonserver to function - global.DirInit(false) + _, err := global.DirInit(false) + if err != nil { + back.PrintError("Failed to initialize libmuttonserver directories: "+err.Error(), back.ErrorWrite, true) + } _ = os.MkdirAll(global.ConfigDir+global.PathSeparator+"deletions", 0700) // error ignored; failure would have occurred by this point in core.DirInit fmt.Println("libmuttonserver directories initialized") case "version": diff --git a/syncclient/client.go b/syncclient/client.go index d816e5c..7203244 100644 --- a/syncclient/client.go +++ b/syncclient/client.go @@ -343,7 +343,7 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows bool, dow // syncLists determines which entries need to be downloaded and uploaded for synchronizations and calls sftpSync with this information. // Using maps means that syncing will be done in an arbitrary order, but it is a worthy tradeoff for speed and simplicity. -func syncLists(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows, timeSynced, returnLists bool, localEntryModMap, remoteEntryModMap map[string]int64) [3][]string { +func syncLists(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows, timeSynced, returnLists bool, localEntryModMap, remoteEntryModMap map[string]int64) ([3][]string, error) { // initialize slices to store entries that need to be downloaded or uploaded var downloadList, uploadList []string @@ -376,7 +376,10 @@ func syncLists(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows, timeSyn // call sftpSync with the download and upload lists if timeSynced && (max(len(downloadList), len(uploadList)) > 0) { // only call sftpSync if there are entries to download or upload fmt.Println() // add a gap between list-add messages and the actual sync messages from sftpSync - sftpSync(sshClient, sshEntryRoot, sshIsWindows, downloadList, uploadList) + err := sftpSync(sshClient, sshEntryRoot, sshIsWindows, downloadList, uploadList) + if err != nil { + return [3][]string{nil, nil, nil}, errors.New("unable to sync entries: " + err.Error()) + } } else if !timeSynced { // do not call sftpSync if the client and server times are out of sync back.Exit(1) @@ -385,9 +388,9 @@ func syncLists(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows, timeSyn fmt.Println("Client is synchronized with server") if returnLists { - return [3][]string{nil, downloadList, uploadList} + return [3][]string{nil, downloadList, uploadList}, nil } - return [3][]string{nil, nil, nil} + return [3][]string{nil, nil, nil}, nil } // deletionSync removes entries from the client that have been deleted on the server (multi-client deletion). @@ -448,10 +451,16 @@ func RunJob(manualSync, returnLists bool) ([3][]string, error) { } // sync deletions - deletionSync(deletions) + err = deletionSync(deletions) + if err != nil { + return [3][]string{nil, nil, nil}, errors.New("unable to sync deletions: " + err.Error()) + } // sync folders - folderSync(remoteFolders) + err = folderSync(remoteFolders) + if err != nil { + return [3][]string{nil, nil, nil}, errors.New("unable to sync folders: " + err.Error()) + } // fetch local lists localEntryModMap, err := getLocalData() @@ -459,7 +468,7 @@ func RunJob(manualSync, returnLists bool) ([3][]string, error) { return [3][]string{nil, nil, nil}, errors.New("unable to fetch local entry data: " + err.Error()) } - // prior to syncing lists, ensure the client and server clocks are synced within 45 seconds + // before syncing lists, ensure the client and server clocks are synced within 45 seconds var timeSynced = true timeDiff := serverTime - clientTime if timeDiff < -45 || timeDiff > 45 { @@ -470,11 +479,17 @@ func RunJob(manualSync, returnLists bool) ([3][]string, error) { // sync new and updated entries var lists [3][]string if returnLists { - lists = syncLists(sshClient, sshEntryRoot, sshIsWindows, timeSynced, true, localEntryModMap, remoteEntryModMap) + lists, err = syncLists(sshClient, sshEntryRoot, sshIsWindows, timeSynced, true, localEntryModMap, remoteEntryModMap) + if err != nil { + return [3][]string{nil, nil, nil}, errors.New("unable to sync entries: " + err.Error()) + } lists[0] = deletions return lists, nil } - syncLists(sshClient, sshEntryRoot, sshIsWindows, timeSynced, false, localEntryModMap, remoteEntryModMap) + _, err = syncLists(sshClient, sshEntryRoot, sshIsWindows, timeSynced, false, localEntryModMap, remoteEntryModMap) + if err != nil { + return [3][]string{nil, nil, nil}, errors.New("unable to sync entries: " + err.Error()) + } back.Exit(0) // exit program if running non-interactively return lists, nil // dummy return for when not returning lists } diff --git a/syncclient/oneOff.go b/syncclient/oneOff.go index 4123a7e..f08043c 100644 --- a/syncclient/oneOff.go +++ b/syncclient/oneOff.go @@ -30,7 +30,10 @@ func ShearRemoteFromClient(targetLocationIncomplete string, forceOffline bool) e } // call the server to remotely shear the target and add it to the deletions list - GetSSHOutput(sshClient, "libmuttonserver shear", deviceID+"\n"+strings.ReplaceAll(targetLocationIncomplete, global.PathSeparator, global.FSPath)) + _, err = GetSSHOutput(sshClient, "libmuttonserver shear", deviceID+"\n"+strings.ReplaceAll(targetLocationIncomplete, global.PathSeparator, global.FSPath)) + if err != nil { + return errors.New("unable to shear target remotely: " + err.Error()) + } // close the SSH client err = sshClient.Close() @@ -46,7 +49,10 @@ func ShearRemoteFromClient(targetLocationIncomplete string, forceOffline bool) e // RenameRemoteFromClient renames oldLocationIncomplete to newLocationIncomplete on the local system and calls the server to perform the rename remotely and add the old target to the deletions list. // It can safely be called in offline mode, as well, so this is the intended interface for renaming (RenameLocal should only be used directly by the server binary). func RenameRemoteFromClient(oldLocationIncomplete, newLocationIncomplete string, forceOffline bool) error { - synccommon.RenameLocal(oldLocationIncomplete, newLocationIncomplete, false) // move the target on the local system + err := synccommon.RenameLocal(oldLocationIncomplete, newLocationIncomplete, false) // move the target on the local system + if err != nil { + return errors.New("unable to rename target locally: " + err.Error()) + } deviceIDList, err := global.GenDeviceIDList() if err != nil { @@ -60,10 +66,13 @@ func RenameRemoteFromClient(oldLocationIncomplete, newLocationIncomplete string, } // call the server to move the target on the remote system and add the old target to the deletions list - GetSSHOutput(sshClient, "libmuttonserver rename", + _, err = GetSSHOutput(sshClient, "libmuttonserver rename", (deviceIDList)[0].Name()+"\n"+ strings.ReplaceAll(oldLocationIncomplete, global.PathSeparator, global.FSPath)+"\n"+ strings.ReplaceAll(newLocationIncomplete, global.PathSeparator, global.FSPath)) + if err != nil { + return errors.New("unable to rename target remotely: " + err.Error()) + } // close the SSH client err = sshClient.Close() @@ -79,7 +88,10 @@ func RenameRemoteFromClient(oldLocationIncomplete, newLocationIncomplete string, // AddFolderRemoteFromClient creates a new entry-containing directory on the local system and calls the server to create the folder remotely. // It can safely be called in offline mode, as well, so this is the intended interface for adding folders (AddFolderLocal should only be used directly by the server binary). func AddFolderRemoteFromClient(targetLocationIncomplete string, forceOffline bool) error { - synccommon.AddFolderLocal(targetLocationIncomplete) // add the folder on the local system + err := synccommon.AddFolderLocal(targetLocationIncomplete) // add the folder on the local system + if err != nil { + return errors.New("unable to add folder locally: " + err.Error()) + } deviceIDList, err := global.GenDeviceIDList() if err != nil { @@ -93,7 +105,10 @@ func AddFolderRemoteFromClient(targetLocationIncomplete string, forceOffline boo } // call the server to create the folder remotely - GetSSHOutput(sshClient, "libmuttonserver addfolder", strings.ReplaceAll(targetLocationIncomplete, global.PathSeparator, global.FSPath)) // call the server to create the folder remotely + _, err = GetSSHOutput(sshClient, "libmuttonserver addfolder", strings.ReplaceAll(targetLocationIncomplete, global.PathSeparator, global.FSPath)) // call the server to create the folder remotely + if err != nil { + return errors.New("unable to add folder remotely: " + err.Error()) + } // close the SSH client err = sshClient.Close() diff --git a/wiki/clipboard.md b/wiki/clipboard.md index 5d1d876..59a0b14 100644 --- a/wiki/clipboard.md +++ b/wiki/clipboard.md @@ -8,4 +8,4 @@ Clipboard managers save a history of what has been copied to the clipboard, whic It is likely other popular clipboard managers exhibit this behavior. I noticed it with KDE Klipper, which is what prompted me to create this wiki page. **Clipboard managers should not be enabled by default in any environment** or distribution due to their **potential security implications**. ### Termux cannot clear the clipboard from the background *** -If using libmutton on Termux (Android), the clipboard may not successfully be cleared after the 30 second timeout period if Termux is not actively in the foreground when the sleep timer expires. This is an unfortunate side-effect of running on Android and cannot be easily fixed. Due to Termux being at the bottom of the platform support priority list, I will not be investing time into working around this. \ No newline at end of file +If using libmutton on Termux (Android), the clipboard may not successfully be cleared after the 30-second timeout period if Termux is not actively in the foreground when the sleep timer expires. This is an unfortunate side effect of running on Android and cannot be easily fixed. Due to Termux being at the bottom of the platform support priority list, I will not be investing time in working around this. \ No newline at end of file