2011-06-01 23:08:05 +00:00
|
|
|
// SparkleShare, a collaboration and sharing tool.
|
2011-05-16 20:19:58 +00:00
|
|
|
// Copyright (C) 2010 Hylke Bons <hylkebons@gmail.com>
|
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
|
|
|
|
using System;
|
2012-04-12 22:44:31 +00:00
|
|
|
using System.Collections.Generic;
|
2012-04-11 18:07:34 +00:00
|
|
|
using System.Diagnostics;
|
2012-01-31 21:30:23 +00:00
|
|
|
using System.IO;
|
2012-08-04 18:55:29 +00:00
|
|
|
using System.Security.Cryptography;
|
2011-05-16 20:19:58 +00:00
|
|
|
using System.Threading;
|
|
|
|
|
|
|
|
namespace SparkleLib {
|
|
|
|
|
|
|
|
// Sets up a fetcher that can get remote folders
|
|
|
|
public abstract class SparkleFetcherBase {
|
|
|
|
|
2012-07-18 10:35:22 +00:00
|
|
|
public event Action Started = delegate { };
|
|
|
|
public event Action Failed = delegate { };
|
|
|
|
|
|
|
|
public event FinishedEventHandler Finished = delegate { };
|
2012-04-22 12:32:55 +00:00
|
|
|
public delegate void FinishedEventHandler (bool repo_is_encrypted, bool repo_is_empty, string [] warnings);
|
2012-07-18 10:35:22 +00:00
|
|
|
|
|
|
|
public event ProgressChangedEventHandler ProgressChanged = delegate { };
|
2011-08-27 15:53:17 +00:00
|
|
|
public delegate void ProgressChangedEventHandler (double percentage);
|
2011-05-16 20:19:58 +00:00
|
|
|
|
|
|
|
|
2012-02-09 01:46:25 +00:00
|
|
|
public abstract bool Fetch ();
|
|
|
|
public abstract void Stop ();
|
2012-04-22 12:32:55 +00:00
|
|
|
public abstract bool IsFetchedRepoEmpty { get; }
|
|
|
|
public abstract bool IsFetchedRepoPasswordCorrect (string password);
|
|
|
|
public abstract void EnableFetchedRepoCrypto (string password);
|
|
|
|
|
|
|
|
public Uri RemoteUrl { get; protected set; }
|
|
|
|
public string RequiredFingerprint { get; protected set; }
|
|
|
|
public readonly bool FetchPriorHistory = false;
|
|
|
|
public string TargetFolder { get; protected set; }
|
2012-02-12 21:25:20 +00:00
|
|
|
public bool IsActive { get; private set; }
|
2012-07-06 09:26:02 +00:00
|
|
|
public string Identifier;
|
2011-06-05 16:35:22 +00:00
|
|
|
|
2012-04-22 12:32:55 +00:00
|
|
|
public string [] Warnings {
|
|
|
|
get {
|
|
|
|
return this.warnings.ToArray ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-06-17 20:56:27 +00:00
|
|
|
public string [] Errors {
|
|
|
|
get {
|
|
|
|
return this.errors.ToArray ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-06-19 09:43:18 +00:00
|
|
|
|
2012-04-22 12:32:55 +00:00
|
|
|
protected List<string> warnings = new List<string> ();
|
2012-07-06 09:26:02 +00:00
|
|
|
protected List<string> errors = new List<string> ();
|
|
|
|
|
|
|
|
protected string [] ExcludeRules = new string [] {
|
|
|
|
"*.autosave", // Various autosaving apps
|
|
|
|
"*~", // gedit and emacs
|
|
|
|
".~lock.*", // LibreOffice
|
|
|
|
"*.part", "*.crdownload", // Firefox and Chromium temporary download files
|
|
|
|
".*.sw[a-z]", "*.un~", "*.swp", "*.swo", // vi(m)
|
|
|
|
".directory", // KDE
|
|
|
|
".DS_Store", "Icon\r\r", "._*", ".Spotlight-V100", ".Trashes", // Mac OS X
|
|
|
|
"*(Autosaved).graffle", // Omnigraffle
|
|
|
|
"Thumbs.db", "Desktop.ini", // Windows
|
|
|
|
"~*.tmp", "~*.TMP", "*~*.tmp", "*~*.TMP", // MS Office
|
|
|
|
"~*.ppt", "~*.PPT", "~*.pptx", "~*.PPTX",
|
|
|
|
"~*.xls", "~*.XLS", "~*.xlsx", "~*.XLSX",
|
|
|
|
"~*.doc", "~*.DOC", "~*.docx", "~*.DOCX",
|
|
|
|
"*/CVS/*", ".cvsignore", "*/.cvsignore", // CVS
|
|
|
|
"/.svn/*", "*/.svn/*", // Subversion
|
|
|
|
"/.hg/*", "*/.hg/*", "*/.hgignore", // Mercurial
|
|
|
|
"/.bzr/*", "*/.bzr/*", "*/.bzrignore" // Bazaar
|
|
|
|
};
|
|
|
|
|
2012-06-17 20:56:27 +00:00
|
|
|
|
2011-08-27 18:57:34 +00:00
|
|
|
private Thread thread;
|
2011-05-16 20:19:58 +00:00
|
|
|
|
2012-01-31 21:30:23 +00:00
|
|
|
|
2012-07-06 09:26:02 +00:00
|
|
|
public SparkleFetcherBase (string server, string required_fingerprint,
|
|
|
|
string remote_path, string target_folder, bool fetch_prior_history)
|
2011-05-16 20:19:58 +00:00
|
|
|
{
|
2012-04-12 22:44:31 +00:00
|
|
|
RequiredFingerprint = required_fingerprint;
|
|
|
|
FetchPriorHistory = fetch_prior_history;
|
|
|
|
remote_path = remote_path.Trim ("/".ToCharArray ());
|
2012-04-11 21:10:02 +00:00
|
|
|
|
|
|
|
if (server.EndsWith ("/"))
|
|
|
|
server = server.Substring (0, server.Length - 1);
|
|
|
|
|
|
|
|
if (!remote_path.StartsWith ("/"))
|
|
|
|
remote_path = "/" + remote_path;
|
|
|
|
|
|
|
|
if (!server.Contains ("://"))
|
|
|
|
server = "ssh://" + server;
|
|
|
|
|
2012-02-09 01:46:25 +00:00
|
|
|
TargetFolder = target_folder;
|
2012-12-19 20:01:55 +00:00
|
|
|
|
2012-12-19 20:58:22 +00:00
|
|
|
RemoteUrl = new Uri (server + remote_path);
|
|
|
|
IsActive = false;
|
2011-05-16 20:19:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void Start ()
|
|
|
|
{
|
2012-02-12 21:25:20 +00:00
|
|
|
IsActive = true;
|
2012-07-18 10:35:22 +00:00
|
|
|
Started ();
|
2011-05-16 20:19:58 +00:00
|
|
|
|
2012-07-28 13:58:09 +00:00
|
|
|
SparkleLogger.LogInfo ("Fetcher", TargetFolder + " | Fetching folder: " + RemoteUrl);
|
2012-04-12 22:44:31 +00:00
|
|
|
|
2012-02-09 01:46:25 +00:00
|
|
|
if (Directory.Exists (TargetFolder))
|
|
|
|
Directory.Delete (TargetFolder, true);
|
2011-05-16 20:19:58 +00:00
|
|
|
|
2012-10-05 19:55:57 +00:00
|
|
|
string host_key = "";
|
2012-04-11 21:10:02 +00:00
|
|
|
|
2012-10-05 19:55:57 +00:00
|
|
|
if (!RemoteUrl.Scheme.StartsWith ("http")) {
|
|
|
|
host_key = FetchHostKey ();
|
|
|
|
|
|
|
|
if (string.IsNullOrEmpty (RemoteUrl.Host) || host_key == null) {
|
2012-12-06 21:35:53 +00:00
|
|
|
SparkleLogger.LogInfo ("Auth", "Could not fetch host key");
|
2012-10-05 19:55:57 +00:00
|
|
|
Failed ();
|
2012-12-06 21:35:53 +00:00
|
|
|
|
2012-10-05 19:55:57 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool warn = true;
|
|
|
|
if (RequiredFingerprint != null) {
|
2012-10-06 11:17:39 +00:00
|
|
|
string host_fingerprint = DeriveFingerprint (host_key);
|
2012-04-12 22:44:31 +00:00
|
|
|
|
2012-10-05 19:55:57 +00:00
|
|
|
if (host_fingerprint == null || !RequiredFingerprint.Equals (host_fingerprint)) {
|
|
|
|
SparkleLogger.LogInfo ("Auth", "Fingerprint doesn't match");
|
2012-04-12 22:44:31 +00:00
|
|
|
|
2012-10-05 19:55:57 +00:00
|
|
|
this.errors.Add ("error: Host fingerprint doesn't match");
|
|
|
|
Failed ();
|
2012-07-27 18:34:43 +00:00
|
|
|
|
2012-10-05 19:55:57 +00:00
|
|
|
return;
|
|
|
|
}
|
2012-04-12 22:44:31 +00:00
|
|
|
|
2012-10-05 19:55:57 +00:00
|
|
|
warn = false;
|
|
|
|
SparkleLogger.LogInfo ("Auth", "Fingerprint matches");
|
2012-04-12 22:44:31 +00:00
|
|
|
|
2012-10-05 19:55:57 +00:00
|
|
|
} else {
|
|
|
|
SparkleLogger.LogInfo ("Auth", "Skipping fingerprint check");
|
|
|
|
}
|
2012-04-12 22:44:31 +00:00
|
|
|
|
2012-10-05 19:55:57 +00:00
|
|
|
AcceptHostKey (host_key, warn);
|
2012-04-12 22:44:31 +00:00
|
|
|
}
|
|
|
|
|
2012-07-22 09:51:56 +00:00
|
|
|
this.thread = new Thread (() => {
|
|
|
|
if (Fetch ()) {
|
|
|
|
Thread.Sleep (500);
|
2012-07-28 13:58:09 +00:00
|
|
|
SparkleLogger.LogInfo ("Fetcher", "Finished");
|
2011-05-31 01:26:57 +00:00
|
|
|
|
2012-07-22 09:51:56 +00:00
|
|
|
IsActive = false;
|
2011-05-16 20:19:58 +00:00
|
|
|
|
2012-11-22 12:31:48 +00:00
|
|
|
bool repo_is_encrypted = (RemoteUrl.AbsolutePath.Contains ("-crypto") ||
|
|
|
|
RemoteUrl.Host.Equals ("sparkleshare.net"));
|
2012-11-04 17:43:29 +00:00
|
|
|
|
2012-07-22 09:51:56 +00:00
|
|
|
Finished (repo_is_encrypted, IsFetchedRepoEmpty, Warnings);
|
2011-05-31 01:26:57 +00:00
|
|
|
|
2012-07-22 09:51:56 +00:00
|
|
|
} else {
|
|
|
|
Thread.Sleep (500);
|
2012-07-28 13:58:09 +00:00
|
|
|
SparkleLogger.LogInfo ("Fetcher", "Failed");
|
2011-05-31 01:26:57 +00:00
|
|
|
|
2012-07-22 09:51:56 +00:00
|
|
|
IsActive = false;
|
|
|
|
Failed ();
|
|
|
|
}
|
|
|
|
});
|
2011-05-16 20:19:58 +00:00
|
|
|
|
|
|
|
this.thread.Start ();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-07-05 11:33:32 +00:00
|
|
|
public virtual void Complete ()
|
|
|
|
{
|
|
|
|
string identifier_path = Path.Combine (TargetFolder, ".sparkleshare");
|
|
|
|
|
2012-07-06 09:26:02 +00:00
|
|
|
if (File.Exists (identifier_path)) {
|
|
|
|
Identifier = File.ReadAllText (identifier_path).Trim ();
|
2012-10-20 22:25:36 +00:00
|
|
|
|
2012-07-06 09:26:02 +00:00
|
|
|
} else {
|
|
|
|
Identifier = CreateIdentifier ();
|
2012-07-05 11:33:32 +00:00
|
|
|
File.WriteAllText (identifier_path, Identifier);
|
|
|
|
|
|
|
|
CreateInitialChangeSet ();
|
2012-10-04 16:51:47 +00:00
|
|
|
}
|
2012-10-20 22:25:36 +00:00
|
|
|
|
|
|
|
File.SetAttributes (identifier_path, FileAttributes.Hidden);
|
2012-07-05 11:33:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Create an initial change set when the
|
|
|
|
// user has fetched an empty remote folder
|
2012-10-04 16:51:47 +00:00
|
|
|
private void CreateInitialChangeSet ()
|
2012-07-05 11:33:32 +00:00
|
|
|
{
|
|
|
|
string file_path = Path.Combine (TargetFolder, "SparkleShare.txt");
|
|
|
|
string n = Environment.NewLine;
|
|
|
|
|
2012-08-16 08:50:35 +00:00
|
|
|
UriBuilder uri_builder = new UriBuilder (RemoteUrl);
|
|
|
|
|
|
|
|
if (RemoteUrl.Scheme.StartsWith ("http")) {
|
|
|
|
uri_builder.UserName = "";
|
|
|
|
uri_builder.Password = "";
|
|
|
|
}
|
|
|
|
|
2012-11-02 16:09:05 +00:00
|
|
|
string text = "Congratulations, you've successfully created a SparkleShare repository!" + n +
|
|
|
|
n +
|
|
|
|
"Any files you add or change in this folder will be automatically synced to " + n +
|
|
|
|
uri_builder.ToString () + " and everyone connected to it." + n +
|
|
|
|
n +
|
|
|
|
"SparkleShare is an Open Source software program that helps people collaborate and " + n +
|
|
|
|
"share files. If you like what we do, consider buying us a beer: http://www.sparkleshare.org/" + n +
|
|
|
|
n +
|
|
|
|
"Have fun! :)" + n;
|
|
|
|
|
2012-11-04 17:43:29 +00:00
|
|
|
if (RemoteUrl.AbsolutePath.Contains ("-crypto") || RemoteUrl.Host.Equals ("sparkleshare.net"))
|
2012-11-02 16:09:05 +00:00
|
|
|
text = text.Replace ("a SparkleShare repository", "an encrypted SparkleShare repository");
|
2012-09-30 11:07:36 +00:00
|
|
|
|
|
|
|
File.WriteAllText (file_path, text);
|
2012-07-05 11:33:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-07-06 09:26:02 +00:00
|
|
|
public static string CreateIdentifier ()
|
|
|
|
{
|
2012-07-08 09:29:00 +00:00
|
|
|
string random = Path.GetRandomFileName ();
|
2012-07-28 13:58:09 +00:00
|
|
|
return random.SHA1 ();
|
2012-07-06 09:26:02 +00:00
|
|
|
}
|
|
|
|
|
2012-10-05 19:55:57 +00:00
|
|
|
|
2011-05-16 20:19:58 +00:00
|
|
|
public void Dispose ()
|
|
|
|
{
|
2012-08-27 12:24:23 +00:00
|
|
|
if (this.thread != null)
|
2011-06-05 16:35:22 +00:00
|
|
|
this.thread.Abort ();
|
2011-05-16 20:19:58 +00:00
|
|
|
}
|
2012-07-05 11:33:32 +00:00
|
|
|
|
|
|
|
|
2011-08-27 15:53:17 +00:00
|
|
|
protected void OnProgressChanged (double percentage) {
|
2012-07-18 10:35:22 +00:00
|
|
|
ProgressChanged (percentage);
|
2011-08-27 15:53:17 +00:00
|
|
|
}
|
2011-05-31 01:26:57 +00:00
|
|
|
|
2011-06-05 18:59:48 +00:00
|
|
|
|
2012-09-29 21:57:35 +00:00
|
|
|
protected string GenerateCryptoSalt ()
|
|
|
|
{
|
2012-10-04 16:51:47 +00:00
|
|
|
string salt = Path.GetRandomFileName ().SHA1 ();
|
2012-09-29 21:57:35 +00:00
|
|
|
return salt.Substring (0, 16);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-10-05 19:55:57 +00:00
|
|
|
private string FetchHostKey ()
|
2011-05-31 01:26:57 +00:00
|
|
|
{
|
2012-11-22 12:31:48 +00:00
|
|
|
SparkleLogger.LogInfo ("Auth", "Fetching host key for " + RemoteUrl.Host);
|
2011-05-31 01:26:57 +00:00
|
|
|
|
2012-11-22 12:31:48 +00:00
|
|
|
Process process = new Process ();
|
2012-07-22 20:54:59 +00:00
|
|
|
process.StartInfo.FileName = "ssh-keyscan";
|
2012-04-11 18:07:34 +00:00
|
|
|
process.StartInfo.WorkingDirectory = SparkleConfig.DefaultConfig.TmpPath;
|
|
|
|
process.StartInfo.UseShellExecute = false;
|
|
|
|
process.StartInfo.RedirectStandardOutput = true;
|
|
|
|
process.StartInfo.CreateNoWindow = true;
|
2012-11-22 12:31:48 +00:00
|
|
|
process.EnableRaisingEvents = true;
|
2011-05-31 01:26:57 +00:00
|
|
|
|
2012-11-22 12:31:48 +00:00
|
|
|
if (RemoteUrl.Port < 1)
|
|
|
|
process.StartInfo.Arguments = "-t rsa -p 22 " + RemoteUrl.Host;
|
|
|
|
else
|
|
|
|
process.StartInfo.Arguments = "-t rsa -p " + RemoteUrl.Port + " " + RemoteUrl.Host;
|
2011-10-30 21:22:48 +00:00
|
|
|
|
2012-12-06 21:35:53 +00:00
|
|
|
SparkleLogger.LogInfo ("Cmd", process.StartInfo.FileName + " " + process.StartInfo.Arguments);
|
|
|
|
|
2012-11-22 12:31:48 +00:00
|
|
|
process.Start ();
|
2012-04-12 22:44:31 +00:00
|
|
|
string host_key = process.StandardOutput.ReadToEnd ().Trim ();
|
2012-04-11 18:07:34 +00:00
|
|
|
process.WaitForExit ();
|
2011-05-31 01:26:57 +00:00
|
|
|
|
2012-09-05 13:14:15 +00:00
|
|
|
if (process.ExitCode == 0 && !string.IsNullOrEmpty (host_key))
|
2012-04-11 18:07:34 +00:00
|
|
|
return host_key;
|
|
|
|
else
|
|
|
|
return null;
|
|
|
|
}
|
2011-10-30 21:22:48 +00:00
|
|
|
|
|
|
|
|
2012-10-06 11:17:39 +00:00
|
|
|
private string DeriveFingerprint (string public_key)
|
2012-04-12 22:44:31 +00:00
|
|
|
{
|
2012-04-26 17:48:18 +00:00
|
|
|
try {
|
2012-08-04 18:55:29 +00:00
|
|
|
MD5 md5 = new MD5CryptoServiceProvider ();
|
|
|
|
string key = public_key.Split (" ".ToCharArray ()) [2];
|
|
|
|
byte [] b64_bytes = Convert.FromBase64String (key);
|
|
|
|
byte [] md5_bytes = md5.ComputeHash (b64_bytes);
|
|
|
|
string fingerprint = BitConverter.ToString (md5_bytes);
|
|
|
|
|
|
|
|
return fingerprint.ToLower ().Replace ("-", ":");
|
2012-04-26 17:48:18 +00:00
|
|
|
|
|
|
|
} catch (Exception e) {
|
2012-11-22 12:31:48 +00:00
|
|
|
SparkleLogger.LogInfo ("Fetcher", "Failed creating fingerprint: " + e.Message + " " + e.StackTrace);
|
2012-04-26 17:48:18 +00:00
|
|
|
return null;
|
|
|
|
}
|
2012-04-12 22:44:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void AcceptHostKey (string host_key, bool warn)
|
2012-04-11 18:07:34 +00:00
|
|
|
{
|
|
|
|
string ssh_config_path = Path.Combine (SparkleConfig.DefaultConfig.HomePath, ".ssh");
|
|
|
|
string known_hosts_file_path = Path.Combine (ssh_config_path, "known_hosts");
|
2011-05-31 01:26:57 +00:00
|
|
|
|
2012-04-11 18:07:34 +00:00
|
|
|
if (!File.Exists (known_hosts_file_path)) {
|
|
|
|
if (!Directory.Exists (ssh_config_path))
|
|
|
|
Directory.CreateDirectory (ssh_config_path);
|
2011-05-31 01:26:57 +00:00
|
|
|
|
2012-04-11 18:07:34 +00:00
|
|
|
File.Create (known_hosts_file_path).Close ();
|
2011-05-31 01:26:57 +00:00
|
|
|
}
|
|
|
|
|
2012-04-11 19:31:18 +00:00
|
|
|
string host = RemoteUrl.Host;
|
|
|
|
string known_hosts = File.ReadAllText (known_hosts_file_path);
|
|
|
|
string [] known_hosts_lines = File.ReadAllLines (known_hosts_file_path);
|
|
|
|
|
|
|
|
foreach (string line in known_hosts_lines) {
|
|
|
|
if (line.StartsWith (host + " "))
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (known_hosts.EndsWith ("\n"))
|
2012-04-11 18:07:34 +00:00
|
|
|
File.AppendAllText (known_hosts_file_path, host_key + "\n");
|
2011-05-31 01:26:57 +00:00
|
|
|
else
|
2012-04-11 18:07:34 +00:00
|
|
|
File.AppendAllText (known_hosts_file_path, "\n" + host_key + "\n");
|
|
|
|
|
2012-07-28 13:58:09 +00:00
|
|
|
SparkleLogger.LogInfo ("Auth", "Accepted host key for " + host);
|
2012-04-12 22:44:31 +00:00
|
|
|
|
2012-04-15 10:35:17 +00:00
|
|
|
if (warn)
|
2012-10-06 11:17:39 +00:00
|
|
|
this.warnings.Add ("The following host key has been accepted:\n" + DeriveFingerprint (host_key));
|
2011-12-25 19:33:39 +00:00
|
|
|
}
|
2012-10-04 16:51:47 +00:00
|
|
|
|
|
|
|
|
2012-11-04 16:27:39 +00:00
|
|
|
public static string GetBackend (string address)
|
2012-10-04 16:51:47 +00:00
|
|
|
{
|
2012-11-04 18:20:01 +00:00
|
|
|
if (address.StartsWith ("ssh+")) {
|
|
|
|
string backend = address.Substring (0, address.IndexOf ("://"));
|
|
|
|
backend = backend.Substring (4);
|
2012-11-04 16:27:39 +00:00
|
|
|
|
|
|
|
return char.ToUpper (backend [0]) + backend.Substring (1);
|
|
|
|
|
2012-10-04 16:51:47 +00:00
|
|
|
} else {
|
|
|
|
return "Git";
|
|
|
|
}
|
|
|
|
}
|
2011-05-16 20:19:58 +00:00
|
|
|
}
|
|
|
|
}
|