diff --git a/Main.go b/Main.go new file mode 100644 index 0000000..2cfed36 --- /dev/null +++ b/Main.go @@ -0,0 +1,58 @@ +package main + +import ( + "fmt" + "github.com/SommerEngineering/Sync/Sync" + "golang.org/x/crypto/ssh" + "log" + "runtime" +) + +func main() { + + // Show the current version: + fmt.Println(`Sync v1.0.0`) + + // Allow Go to use all CPUs: + runtime.GOMAXPROCS(runtime.NumCPU()) + + // Read the configuration from the command-line args: + readFlags() + + // Check if the directories are provided: + if localDir == `` || remoteDir == `` { + log.Println(`Please provide the local and remote directory.`) + return + } + + // Check if the password was provided: + for true { + if password == `` { + // Promt for the password: + fmt.Println(`Please provide the password for the connection:`) + fmt.Scanln(&password) + } else { + break + } + } + + // Create the SSH configuration: + Sync.SetPassword4Callback(password) + config := &ssh.ClientConfig{ + User: username, + Auth: []ssh.AuthMethod{ + ssh.Password(password), + ssh.PasswordCallback(Sync.PasswordCallback), + ssh.KeyboardInteractive(Sync.KeyboardInteractiveChallenge), + }, + } + + // Connect to the SSH server: + ssh := Sync.ConnectSSH(config, serverAddrString) + if ssh == nil { + log.Println(`It was not possible to connect to the SSH server.`) + return + } + + defer ssh.Close() +} diff --git a/ReadFlags.go b/ReadFlags.go new file mode 100644 index 0000000..5b9365e --- /dev/null +++ b/ReadFlags.go @@ -0,0 +1,15 @@ +package main + +import ( + "flag" +) + +func readFlags() { + flag.StringVar(&serverAddrString, `server`, `127.0.0.1:22`, `The (remote) SSH server, e.g. 'my.host.com', 'my.host.com:22', '127.0.0.1:22', 'localhost:22'.`) + flag.StringVar(&username, `user`, `username`, `The user's name for the SSH server.`) + flag.StringVar(&password, `pwd`, ``, `The user's password for the SSH server. You can omit these argument. Thus, the program asks for the password on demand.`) + flag.StringVar(&localDir, `localDir`, ``, `The local directory which should be synced.`) + flag.StringVar(&remoteDir, `remoteDir`, ``, `The remote directory which should be synced.`) + flag.BoolVar(&supervised, `supervised`, true, `Use the supervised mode? The algorithm asks you before any change.`) + flag.Parse() +} diff --git a/Sync/ComparePath.go b/Sync/ComparePath.go new file mode 100644 index 0000000..e82c27f --- /dev/null +++ b/Sync/ComparePath.go @@ -0,0 +1,18 @@ +package Sync + +import ( + "path/filepath" + "strings" +) + +func comparePath(localBase, localPath, remoteBase, remotePath string) bool { + localCompare := normalisePath(localBase, localPath) + remoteCompare := normalisePath(remoteBase, remotePath) + return localCompare == remoteCompare +} + +func normalisePath(base, path string) string { + result := strings.Replace(path, base, ``, 1) + result = filepath.ToSlash(result) + return strings.ToLower(result) +} diff --git a/Sync/Connect.go b/Sync/Connect.go new file mode 100644 index 0000000..44c7bab --- /dev/null +++ b/Sync/Connect.go @@ -0,0 +1,42 @@ +package Sync + +import ( + "golang.org/x/crypto/ssh" + "log" + "time" +) + +func ConnectSSH(config *ssh.ClientConfig, serverAddrString string) (sshClientConnection *ssh.Client) { + + currentRetriesServer := 0 + + // Loop for retries: + for { + + // Try to connect to the SSH server: + if sshClientConn, err := ssh.Dial(`tcp`, serverAddrString, config); err != nil { + + // Failed: + currentRetriesServer++ + log.Printf("Was not able to connect with the SSH server %s: %s\n", serverAddrString, err.Error()) + + // Is a retry alowed? + if currentRetriesServer < maxRetriesServer { + log.Println(`Retry...`) + time.Sleep(1 * time.Second) + } else { + + // After the return, this thread is closed down. The client can try it again... + log.Println(`No more retries for connecting the SSH server.`) + return + } + + } else { + + // Success: + log.Println(`Connected to the SSH server ` + serverAddrString) + sshClientConnection = sshClientConn + return + } + } +} diff --git a/Sync/Constants.go b/Sync/Constants.go new file mode 100644 index 0000000..8219c7e --- /dev/null +++ b/Sync/Constants.go @@ -0,0 +1,5 @@ +package Sync + +const ( + maxRetriesServer = 16 // How many retries are allowed to create the SSH server's connection? +) diff --git a/Sync/KeyboardInteractiveChallenge.go b/Sync/KeyboardInteractiveChallenge.go new file mode 100644 index 0000000..c59f988 --- /dev/null +++ b/Sync/KeyboardInteractiveChallenge.go @@ -0,0 +1,35 @@ +package Sync + +import ( + "log" +) + +// Another auth. method. +func KeyboardInteractiveChallenge(user, instruction string, questions []string, echos []bool) (answers []string, err error) { + + // Log all the provided data: + log.Println(`User: ` + user) + log.Println(`Instruction: ` + instruction) + log.Println(`Questions:`) + for q := range questions { + log.Println(q) + } + + // How many questions are asked? + countQuestions := len(questions) + + if countQuestions == 1 { + + // We expect that in this case (only one question is asked), that the server want to know the password ;-) + answers = make([]string, countQuestions, countQuestions) + answers[0] = callbackPassword + + } else if countQuestions > 1 { + + // After logging, this call will exit the whole program: + log.Fatalln(`The SSH server is asking multiple questions! This program cannot handle this case.`) + } + + err = nil + return +} diff --git a/Sync/PasswordCallback.go b/Sync/PasswordCallback.go new file mode 100644 index 0000000..502a2d0 --- /dev/null +++ b/Sync/PasswordCallback.go @@ -0,0 +1,10 @@ +package Sync + +// Just a callback function for the password request. +func PasswordCallback() (string, error) { + return callbackPassword, nil +} + +func SetPassword4Callback(password string) { + callbackPassword = password +} diff --git a/Sync/Synchronise.go b/Sync/Synchronise.go new file mode 100644 index 0000000..2bf033a --- /dev/null +++ b/Sync/Synchronise.go @@ -0,0 +1,48 @@ +package Sync + +import ( + "github.com/pkg/sftp" + "golang.org/x/crypto/ssh" +) + +func Synchronise(ssh *ssh.Client, supervised bool, localDir, remoteDir string) { + /* + Algorithm: + - Get all local files and dirs + - Get all remote files and dirs + - Determine: ndl := newest mod time of all local files + - Determine: ndr := newest mod time of all remote files + - Free remote space + - Are remote files present, which are locally not present + and these remote files are older than ndl? + - Meaning: You have worked locally and remotely are older files which are not locally present. + Interpretation of change: You have deleted these files locally, because you own local + files which are newer that these files. + - delete these remote files! (may ask the user) + - Free local space + - Are local files present, which are remotely not present + and these local files are older than ndr? + - Meaning: You have worked remotely (or have synced with another PC), thus, local files are older + and these are not present remotely. Interpretation of change: You have delete these files + remotely, because your remotely files are newer that these files. + - delete these local files! (may ask the user) + - Download any new files + - Are remote files present, which are locally not present + and these remote files are newer than ndl? + - Meaning: These files are new to the local side + - Download these files! (may ask the user) + - Upload any new files + - Are local files present, which are remotely not present + and these local files are newer than ndr? + - Meaning: These files are new to the remote side + - Upload these files! (may ask the user) + - Changed remote files + - Are remote and local files present, where the remote file is newer? + - Meaning: These files are changed on the remote side + - Download these files and replace the local copies (may ask the user) + - Changed local files + - Are local and remote files present, where the local file is newer? + - Meaning: These files are changed on the local side + - Upload these files and replace the remote copies (may ask the user) + */ +} diff --git a/Sync/Variables.go b/Sync/Variables.go new file mode 100644 index 0000000..1c3b7df --- /dev/null +++ b/Sync/Variables.go @@ -0,0 +1,5 @@ +package Sync + +var ( + callbackPassword = `` +) diff --git a/Variables.go b/Variables.go new file mode 100644 index 0000000..82592f4 --- /dev/null +++ b/Variables.go @@ -0,0 +1,10 @@ +package main + +var ( + username = `` // The SSH user's name + password = `` // The user's password + serverAddrString = `` // The SSH server address + localDir = `` // The local directory + remoteDir = `` // The remote directory + supervised = true // Should the tool work supervised? +)