From c57d42485d25e3824892579924a993deb2879db7 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Wed, 7 Jul 2010 23:55:50 +0100 Subject: [PATCH 01/37] [sparklediff] Change layout a bit --- SparkleDiff/RevisionView.cs | 3 +-- SparkleDiff/SparkleDiffWindow.cs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/SparkleDiff/RevisionView.cs b/SparkleDiff/RevisionView.cs index 24e95f5e..6c13d4eb 100644 --- a/SparkleDiff/RevisionView.cs +++ b/SparkleDiff/RevisionView.cs @@ -68,11 +68,10 @@ namespace SparkleShare { ButtonNext.Clicked += NextInComboBox; ButtonNext.ExposeEvent += EqualizeSizes; - controls.PackStart (new Label (""), true, false, 0); controls.PackStart (ButtonPrevious, false, false, 0); controls.PackStart (ButtonNext, false, false, 0); - controls.PackStart (ComboBox, false, false, 9); controls.PackStart (new Label (""), true, false, 0); + controls.PackStart (ComboBox, false, false, 0); PackStart (controls, false, false, 0); PackStart (ScrolledWindow, true, true, 0); diff --git a/SparkleDiff/SparkleDiffWindow.cs b/SparkleDiff/SparkleDiffWindow.cs index bf27f823..e3098111 100644 --- a/SparkleDiff/SparkleDiffWindow.cs +++ b/SparkleDiff/SparkleDiffWindow.cs @@ -161,7 +161,7 @@ namespace SparkleShare { private void ResizeToViews () { - int new_width = ViewLeft.GetImage ().Pixbuf.Width + ViewRight.GetImage ().Pixbuf.Width + 100; + int new_width = ViewLeft.GetImage ().Pixbuf.Width + ViewRight.GetImage ().Pixbuf.Width + 200; int new_height = 200; if (ViewLeft.GetImage ().Pixbuf.Height > ViewRight.GetImage ().Pixbuf.Height) From 0f411eaba95fba9d59b7e8b62613ee7f2d19fba2 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Thu, 8 Jul 2010 21:11:26 +0100 Subject: [PATCH 02/37] [sparklediff] Show help when there is no argument given --- SparkleDiff/SparkleDiff.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/SparkleDiff/SparkleDiff.cs b/SparkleDiff/SparkleDiff.cs index b8eb97f0..831406d1 100644 --- a/SparkleDiff/SparkleDiff.cs +++ b/SparkleDiff/SparkleDiff.cs @@ -123,6 +123,10 @@ namespace SparkleShare { } + } else { + + ShowHelp (); + } } @@ -162,7 +166,7 @@ namespace SparkleShare { Console.WriteLine (" "); Console.WriteLine (_("SparkleDiff let's you compare revisions of an image file side by side.")); Console.WriteLine (" "); - Console.WriteLine (_("Usage: sparklediff [FILE]")); + Console.WriteLine (_("Usage: sparklediff [PATH]")); Console.WriteLine (_("Open an image file to show its revisions")); Console.WriteLine (" "); Console.WriteLine (_("Arguments:")); From b287e0bd833e0d48e8404c661c360299bacee5ec Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Thu, 8 Jul 2010 21:13:07 +0100 Subject: [PATCH 03/37] [sparklediff] Allow giving relative paths as arguments --- SparkleDiff/SparkleDiff.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SparkleDiff/SparkleDiff.cs b/SparkleDiff/SparkleDiff.cs index 831406d1..4d330b24 100644 --- a/SparkleDiff/SparkleDiff.cs +++ b/SparkleDiff/SparkleDiff.cs @@ -95,7 +95,7 @@ namespace SparkleShare { Environment.Exit (0); } - string file_path = args [0]; + string file_path = System.IO.Path.GetFullPath (args [0]); if (File.Exists (file_path)) { @@ -166,7 +166,7 @@ namespace SparkleShare { Console.WriteLine (" "); Console.WriteLine (_("SparkleDiff let's you compare revisions of an image file side by side.")); Console.WriteLine (" "); - Console.WriteLine (_("Usage: sparklediff [PATH]")); + Console.WriteLine (_("Usage: sparklediff [FILE]")); Console.WriteLine (_("Open an image file to show its revisions")); Console.WriteLine (" "); Console.WriteLine (_("Arguments:")); From 703735e1192a1c3e9fe97738ba06c41e36e293e3 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Sat, 10 Jul 2010 16:26:06 +0100 Subject: [PATCH 04/37] Add image for first start screen --- data/side-splash.png | Bin 0 -> 69398 bytes data/side-splash.svg | 2459 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 2459 insertions(+) create mode 100644 data/side-splash.png create mode 100644 data/side-splash.svg diff --git a/data/side-splash.png b/data/side-splash.png new file mode 100644 index 0000000000000000000000000000000000000000..b6125096b89d75252911291f0cc50dbcba1e41ce GIT binary patch literal 69398 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 98ba013f1a591482c80fdfcb1bfbfab7ea129b8a Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Wed, 7 Jul 2010 23:55:50 +0100 Subject: [PATCH 05/37] [sparklediff] Change layout a bit --- SparkleDiff/RevisionView.cs | 3 +-- SparkleDiff/SparkleDiffWindow.cs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/SparkleDiff/RevisionView.cs b/SparkleDiff/RevisionView.cs index 24e95f5e..6c13d4eb 100644 --- a/SparkleDiff/RevisionView.cs +++ b/SparkleDiff/RevisionView.cs @@ -68,11 +68,10 @@ namespace SparkleShare { ButtonNext.Clicked += NextInComboBox; ButtonNext.ExposeEvent += EqualizeSizes; - controls.PackStart (new Label (""), true, false, 0); controls.PackStart (ButtonPrevious, false, false, 0); controls.PackStart (ButtonNext, false, false, 0); - controls.PackStart (ComboBox, false, false, 9); controls.PackStart (new Label (""), true, false, 0); + controls.PackStart (ComboBox, false, false, 0); PackStart (controls, false, false, 0); PackStart (ScrolledWindow, true, true, 0); diff --git a/SparkleDiff/SparkleDiffWindow.cs b/SparkleDiff/SparkleDiffWindow.cs index bf27f823..e3098111 100644 --- a/SparkleDiff/SparkleDiffWindow.cs +++ b/SparkleDiff/SparkleDiffWindow.cs @@ -161,7 +161,7 @@ namespace SparkleShare { private void ResizeToViews () { - int new_width = ViewLeft.GetImage ().Pixbuf.Width + ViewRight.GetImage ().Pixbuf.Width + 100; + int new_width = ViewLeft.GetImage ().Pixbuf.Width + ViewRight.GetImage ().Pixbuf.Width + 200; int new_height = 200; if (ViewLeft.GetImage ().Pixbuf.Height > ViewRight.GetImage ().Pixbuf.Height) From c98f3944632e6c4994fd001ba482a0d10fc6b475 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Thu, 8 Jul 2010 21:11:26 +0100 Subject: [PATCH 06/37] [sparklediff] Show help when there is no argument given --- SparkleDiff/SparkleDiff.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/SparkleDiff/SparkleDiff.cs b/SparkleDiff/SparkleDiff.cs index b8eb97f0..831406d1 100644 --- a/SparkleDiff/SparkleDiff.cs +++ b/SparkleDiff/SparkleDiff.cs @@ -123,6 +123,10 @@ namespace SparkleShare { } + } else { + + ShowHelp (); + } } @@ -162,7 +166,7 @@ namespace SparkleShare { Console.WriteLine (" "); Console.WriteLine (_("SparkleDiff let's you compare revisions of an image file side by side.")); Console.WriteLine (" "); - Console.WriteLine (_("Usage: sparklediff [FILE]")); + Console.WriteLine (_("Usage: sparklediff [PATH]")); Console.WriteLine (_("Open an image file to show its revisions")); Console.WriteLine (" "); Console.WriteLine (_("Arguments:")); From 7ffaec222d21159ea3273fedabfa3bfa1f2746c8 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Thu, 8 Jul 2010 21:13:07 +0100 Subject: [PATCH 07/37] [sparklediff] Allow giving relative paths as arguments --- SparkleDiff/SparkleDiff.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SparkleDiff/SparkleDiff.cs b/SparkleDiff/SparkleDiff.cs index 831406d1..4d330b24 100644 --- a/SparkleDiff/SparkleDiff.cs +++ b/SparkleDiff/SparkleDiff.cs @@ -95,7 +95,7 @@ namespace SparkleShare { Environment.Exit (0); } - string file_path = args [0]; + string file_path = System.IO.Path.GetFullPath (args [0]); if (File.Exists (file_path)) { @@ -166,7 +166,7 @@ namespace SparkleShare { Console.WriteLine (" "); Console.WriteLine (_("SparkleDiff let's you compare revisions of an image file side by side.")); Console.WriteLine (" "); - Console.WriteLine (_("Usage: sparklediff [PATH]")); + Console.WriteLine (_("Usage: sparklediff [FILE]")); Console.WriteLine (_("Open an image file to show its revisions")); Console.WriteLine (" "); Console.WriteLine (_("Arguments:")); From 3b525009c409a4ad09c3ceffcd2fb7e2bce036d8 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Sat, 10 Jul 2010 16:26:06 +0100 Subject: [PATCH 08/37] Add image for first start screen --- data/side-splash.png | Bin 0 -> 69398 bytes data/side-splash.svg | 2459 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 2459 insertions(+) create mode 100644 data/side-splash.png create mode 100644 data/side-splash.svg diff --git a/data/side-splash.png b/data/side-splash.png new file mode 100644 index 0000000000000000000000000000000000000000..b6125096b89d75252911291f0cc50dbcba1e41ce GIT binary patch literal 69398 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 838cab486ec3ba714a7cf2f212cb23fe8748c098 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Tue, 13 Jul 2010 19:23:59 +0100 Subject: [PATCH 09/37] Change of sparklediff UI and start of first start screen --- SparkleDiff/RevisionView.cs | 270 ++++++++++++++++++++----------- SparkleDiff/SparkleDiffWindow.cs | 171 +++++++++++++++----- SparkleShare/Makefile.am | 1 + SparkleShare/SparkleHelpers.cs | 15 +- SparkleShare/SparkleIntro.cs | 137 ++++++++++++++++ SparkleShare/SparklePaths.cs | 26 +-- SparkleShare/SparkleUI.cs | 9 +- SparkleShare/SparkleWindow.cs | 3 +- 8 files changed, 467 insertions(+), 165 deletions(-) create mode 100644 SparkleShare/SparkleIntro.cs diff --git a/SparkleDiff/RevisionView.cs b/SparkleDiff/RevisionView.cs index 6c13d4eb..328aa989 100644 --- a/SparkleDiff/RevisionView.cs +++ b/SparkleDiff/RevisionView.cs @@ -14,7 +14,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +// TODO: Hand cursor when hovering icon view items +// TODO: Use theme colours + using Gtk; +using Mono.Unix; using System; namespace SparkleShare { @@ -24,142 +28,224 @@ namespace SparkleShare { public class RevisionView : VBox { - public ScrolledWindow ScrolledWindow; - public ComboBox ComboBox; - public Button ButtonPrevious; - public Button ButtonNext; - - private int ValueCount; - private Image Image; + // Short alias for the translations + public static string _ (string s) + { + return Catalog.GetString (s); + } - public RevisionView (string [] revisions) : base (false, 6) + + public ScrolledWindow ScrolledWindow; + public IconView IconView; + + private ToggleButton ToggleButton; + private Viewport Viewport; + private ListStore Store; + private Image Image; + private int Selected; + private int Count; + + + public RevisionView () : base (false, 0) { - Image = new Image (); + Count = 0; + Selected = 0; + + ToggleButton = new ToggleButton (); + ToggleButton.Clicked += ToggleView; + ToggleButton.Relief = ReliefStyle.None; ScrolledWindow = new ScrolledWindow (); - ScrolledWindow.AddWithViewport (Image); + Viewport = new Viewport (); + Viewport.Add (new Label ("")); - HBox controls = new HBox (false, 3); - controls.BorderWidth = 0; - - Arrow arrow_left = new Arrow (ArrowType.Left, ShadowType.None); - ButtonPrevious = new Button (); - ButtonPrevious.Add (arrow_left); - ButtonPrevious.Clicked += PreviousInComboBox; - ButtonPrevious.ExposeEvent += EqualizeSizes; + Store = new ListStore(typeof (Gdk.Pixbuf), + typeof (string), + typeof (int)); - ValueCount = 0; + IconView = new IconView (Store); + IconView.SelectionChanged += ChangeSelection; + IconView.MarkupColumn = 1; + IconView.Margin = 12; + IconView.Orientation = Orientation.Horizontal; + IconView.PixbufColumn = 0; + IconView.Spacing = 12; + + Image = new Image (); - ComboBox = ComboBox.NewText (); - - foreach (string revision in revisions) { - ComboBox.AppendText (revision); - } - - ComboBox.Active = 0; - - ValueCount = revisions.Length; - - Arrow arrow_right = new Arrow (ArrowType.Right, ShadowType.None); - ButtonNext = new Button (); - ButtonNext.Add (arrow_right); - ButtonNext.Clicked += NextInComboBox; - ButtonNext.ExposeEvent += EqualizeSizes; - - controls.PackStart (ButtonPrevious, false, false, 0); - controls.PackStart (ButtonNext, false, false, 0); - controls.PackStart (new Label (""), true, false, 0); - controls.PackStart (ComboBox, false, false, 0); - - PackStart (controls, false, false, 0); + ScrolledWindow.Add (Viewport); PackStart (ScrolledWindow, true, true, 0); - Shown += delegate { - UpdateControls (); - }; - - } - - - // Equalizes the height and width of a button when exposed - private void EqualizeSizes (object o, ExposeEventArgs args) { - - Button button = (Button) o; - button.WidthRequest = button.Allocation.Height; - } - public void NextInComboBox (object o, EventArgs args) { + // Changes the selection and enforces a policy of always having something selected + public void ChangeSelection (object o, EventArgs args) + { - if (ComboBox.Active - 1 >= 0) - ComboBox.Active--; + if (IconView.SelectedItems.Length > 0) { -// UpdateControls (); + TreeIter iter; + Store.GetIter (out iter, IconView.SelectedItems [0]); + SetSelected ((int) Store.GetValue (iter, 2)); - } + } else { + + IconView.SelectPath (new TreePath (GetSelected ().ToString())); + + } + } - public void PreviousInComboBox (object o, EventArgs args) { - if (ComboBox.Active + 1 < ValueCount) - ComboBox.Active++; + // Makes sure everything is in place before showing the widget + new public void ShowAll () + { -// UpdateControls (); + if (Children.Length == 2) { + + ToggleButton = (ToggleButton) Children [0]; + ToggleButton.Remove (ToggleButton.Child); + + } else { + + ToggleButton = new ToggleButton (); + ToggleButton.Relief = ReliefStyle.None; + ToggleButton.Clicked += ToggleView; + PackStart (ToggleButton, false, false, 6); + + } + + HBox layout_horizontal = new HBox (false, 12); + layout_horizontal.BorderWidth = 6; + + TreeIter iter; + Store.GetIter (out iter, new TreePath (GetSelected ().ToString())); + + string text = (string) Store.GetValue (iter, 1); + Gdk.Pixbuf pixbuf = (Gdk.Pixbuf) Store.GetValue (iter, 0); + + Label label = new Label (text); + label.UseMarkup = true; + + Arrow arrow_down = new Arrow (ArrowType.Down, ShadowType.None); + + layout_horizontal.PackStart (new Image (pixbuf), false, false, 0); + layout_horizontal.PackStart (label, false, false, 0); + layout_horizontal.PackStart (new Label (""), true, true, 0); + layout_horizontal.PackStart (arrow_down, false, false, 0); + + ToggleButton.Add (layout_horizontal); + ReorderChild (ToggleButton, 0); + + TreePath path = new TreePath (Selected.ToString()); + IconView.SelectPath (path); + + base.ShowAll (); } - // Updates the buttons to be disabled or enabled when needed - public void UpdateControls () { + // Adds a revision to the combobox + public void AddRow (Gdk.Pixbuf pixbuf, string header, string subtext) + { - ButtonPrevious.State = StateType.Normal; - ButtonNext.State = StateType.Normal; + Store.AppendValues (pixbuf, "" + header + "\n" + subtext + "", Count); + IconView.Model = Store; + Count++; - // TODO: Disable Next or Previous buttons when at the first or last value of the combobox - // I can't get this to work! >:( + } + + + // Toggles between a displayed image and a list of revisions + public void ToggleView (object o, EventArgs args) + { + + Viewport.Remove (Viewport.Child); + + if (ToggleButton.Active) { + + Viewport.Add (IconView); + TreePath path = new TreePath (GetSelected ().ToString()); + + IconView.ScrollToPath (path, (float) 0.5, (float) 0.5); + + } else { + + Viewport.Add (Image); - if (ComboBox.Active == ValueCount - 1) { - ButtonPrevious.State = StateType.Insensitive; } - - if (ComboBox.Active == 0) { - ButtonNext.State = StateType.Insensitive; - } - + + ShowAll (); } // Changes the image that is viewed - public void SetImage (Image image) { + public void SetImage (Image image) + { Image = image; - Remove (ScrolledWindow); - ScrolledWindow = new ScrolledWindow (); - ScrolledWindow.AddWithViewport (Image); - Add (ScrolledWindow); + Viewport.Remove (Viewport.Child); + Viewport.Add (Image); + ToggleButton.Active = false; ShowAll (); } - + + + // Returns the image that is currently viewed public Image GetImage () { return Image; } + + + // Selects an item by number + public bool SetSelected (int i) + { + + if (i > -1 && i <= Count) { + Selected = i; + return true; + } + + return false; + + } + + + // Returns the number of the currently selected item + public int GetSelected () + { + return Selected; + } + + + // Looks up an icon from the system's theme + public Gdk.Pixbuf GetIcon (string name, int size) + { + IconTheme icon_theme = new IconTheme (); + icon_theme.AppendSearchPath (System.IO.Path.Combine ("/usr/share/sparkleshare", "icons")); + return icon_theme.LoadIcon (name, size, IconLookupFlags.GenericFallback); + } } // Derived class for the image view on the left - public class LeftRevisionView : RevisionView { - - public LeftRevisionView (string [] revisions) : base (revisions) { + public class LeftRevisionView : RevisionView + { - ComboBox.Active = 1; + public LeftRevisionView () : base () + { + // Select the second revision + SetSelected (1); + + // Take reading direction for time into account if (Direction == Gtk.TextDirection.Ltr) ScrolledWindow.Placement = CornerType.TopRight; else @@ -171,12 +257,15 @@ namespace SparkleShare { // Derived class for the image view on the right - public class RightRevisionView : RevisionView { - - public RightRevisionView (string [] revisions) : base (revisions) { + public class RightRevisionView : RevisionView + { - ComboBox.Active = 0; + public RightRevisionView () : base () + { + SetSelected (0); + + // Take reading direction for time into account if (Direction == Gtk.TextDirection.Ltr) ScrolledWindow.Placement = CornerType.TopLeft; else @@ -186,5 +275,4 @@ namespace SparkleShare { } - } diff --git a/SparkleDiff/SparkleDiffWindow.cs b/SparkleDiff/SparkleDiffWindow.cs index e3098111..248f27b4 100644 --- a/SparkleDiff/SparkleDiffWindow.cs +++ b/SparkleDiff/SparkleDiffWindow.cs @@ -20,6 +20,11 @@ using System; using System.Diagnostics; using System.Text.RegularExpressions; +using System.IO; +using System.Net; +using System.Security.Cryptography; +using System.Text; + namespace SparkleShare { // The main window for SparkleDiff @@ -54,7 +59,7 @@ namespace SparkleShare { VBox layout_vertical = new VBox (false, 12); - HBox layout_horizontal = new HBox (false, 12); + HBox layout_horizontal = new HBox (true, 6); Process process = new Process (); process.EnableRaisingEvents = true; @@ -63,9 +68,12 @@ namespace SparkleShare { process.StartInfo.WorkingDirectory = System.IO.Path.GetDirectoryName (file_path); process.StartInfo.FileName = "git"; - process.StartInfo.Arguments = "log --format=\"%ct\t%an\" " + file_name; + process.StartInfo.Arguments = "log --format=\"%ct\t%an\t%ae\" " + file_name; process.Start (); + ViewLeft = new LeftRevisionView (); + ViewRight = new RightRevisionView (); + string output = process.StandardOutput.ReadToEnd (); string [] revisions_info = Regex.Split (output.Trim (), "\n"); @@ -76,59 +84,54 @@ namespace SparkleShare { int timestamp = int.Parse (parts [0]); string author = parts [1]; + string email = parts [2]; + string date; + // TRANSLATORS: This is a format specifier according to System.Globalization.DateTimeFormatInfo if (i == 0) - revisions_info [i] = _("Current Revision") + "\t" + author; + date = "Latest Revision"; else + date = String.Format (_("{0} at {1}"), + UnixTimestampToDateTime (timestamp).ToString (_("ddd MMM d, yyyy")), + UnixTimestampToDateTime (timestamp).ToString (_("H:mm"))); - // TRANSLATORS: This is a format specifier according to System.Globalization.DateTimeFormatInfo - revisions_info [i] = UnixTimestampToDateTime (timestamp).ToString (_("d MMM\tH:mm")) + - "\t" + author; - + ViewLeft.AddRow (GetAvatar (email, 32), author, date); + ViewRight.AddRow (GetAvatar (email, 32), author, date); + i++; } - ViewLeft = new LeftRevisionView (revisions_info); - ViewRight = new RightRevisionView (revisions_info); ViewLeft.SetImage (new RevisionImage (file_path, Revisions [1])); ViewRight.SetImage (new RevisionImage (file_path, Revisions [0])); + + ViewLeft.IconView.SelectionChanged += delegate { + + ViewLeft.SetImage (new RevisionImage (file_path, Revisions [ViewLeft.GetSelected ()])); + + ViewLeft.ScrolledWindow.Hadjustment = ViewRight.ScrolledWindow.Hadjustment; + ViewLeft.ScrolledWindow.Vadjustment = ViewRight.ScrolledWindow.Vadjustment; + + HookUpViews (); + + }; + + ViewRight.IconView.SelectionChanged += delegate { + + ViewRight.SetImage (new RevisionImage (file_path, Revisions [ViewRight.GetSelected ()])); + + ViewRight.ScrolledWindow.Hadjustment = ViewLeft.ScrolledWindow.Hadjustment; + ViewRight.ScrolledWindow.Vadjustment = ViewLeft.ScrolledWindow.Vadjustment; + + HookUpViews (); + + }; layout_horizontal.PackStart (ViewLeft); layout_horizontal.PackStart (ViewRight); - ViewLeft.ComboBox.Changed += delegate { - - RevisionImage revision_image; - revision_image = new RevisionImage (file_path, Revisions [ViewLeft.ComboBox.Active]); - ViewLeft.SetImage (revision_image); - - HookUpViews (); - - ViewLeft.ScrolledWindow.Hadjustment = ViewRight.ScrolledWindow.Hadjustment; - ViewLeft.ScrolledWindow.Vadjustment = ViewRight.ScrolledWindow.Vadjustment; - - ViewLeft.UpdateControls (); - - }; - - ViewRight.ComboBox.Changed += delegate { - - RevisionImage revision_image; - revision_image = new RevisionImage (file_path, Revisions [ViewRight.ComboBox.Active]); - ViewRight.SetImage (revision_image); - - HookUpViews (); - - ViewRight.ScrolledWindow.Hadjustment = ViewLeft.ScrolledWindow.Hadjustment; - ViewRight.ScrolledWindow.Vadjustment = ViewLeft.ScrolledWindow.Vadjustment; - - ViewRight.UpdateControls (); - - }; - ResizeToViews (); @@ -156,6 +159,22 @@ namespace SparkleShare { Add (layout_vertical); } + + // Converts a UNIX timestamp to a more usable time object + public DateTime UnixTimestampToDateTime (int timestamp) + { + DateTime unix_epoch = new DateTime (1970, 1, 1, 0, 0, 0, 0); + return unix_epoch.AddSeconds (timestamp); + } + + + // Looks up an icon from the system's theme + public Gdk.Pixbuf GetIcon (string name, int size) + { + IconTheme icon_theme = new IconTheme (); + icon_theme.AppendSearchPath (System.IO.Path.Combine ("/usr/share/sparkleshare", "icons")); + return icon_theme.LoadIcon (name, size, IconLookupFlags.GenericFallback); + } private void ResizeToViews () @@ -216,14 +235,80 @@ namespace SparkleShare { } - // Converts a UNIX timestamp to a more usable time object - public DateTime UnixTimestampToDateTime (int timestamp) + public string CombineMore (params string [] Parts) { - DateTime unix_epoch = new DateTime (1970, 1, 1, 0, 0, 0, 0); - return unix_epoch.AddSeconds (timestamp); + string NewPath = " "; + foreach (string Part in Parts) + NewPath = System.IO.Path.Combine (NewPath, Part); + return NewPath; } + // Creates an MD5 hash of input + public static string GetMD5 (string s) + { + MD5 md5 = new MD5CryptoServiceProvider (); + Byte[] bytes = ASCIIEncoding.Default.GetBytes (s); + Byte[] encodedBytes = md5.ComputeHash (bytes); + return BitConverter.ToString (encodedBytes).ToLower ().Replace ("-", ""); + } + + + // TODO: Turn this into an avatar fetching library + // TODO: This should be included from SparkleHelpers, but I don't know how to do that + // Gets the avatar for a specific email address and size + public Gdk.Pixbuf GetAvatar (string Email, int Size) + { + + + UnixUserInfo UnixUserInfo = new UnixUserInfo (UnixEnvironment.UserName); + + string HomePath = UnixUserInfo.HomeDirectory; + + string SparkleLocalIconPath = CombineMore (HomePath + "/.icons", "sparkleshare"); + + string AvatarPath = CombineMore (SparkleLocalIconPath, Size + "x" + Size, "status"); + + if (!Directory.Exists (AvatarPath)) { + Directory.CreateDirectory (AvatarPath); +// SparkleHelpers.DebugInfo ("Config", "Created '" + AvatarPath + "'"); + } + + string AvatarFilePath = CombineMore (AvatarPath, Email); + + if (File.Exists (AvatarFilePath)) + return new Gdk.Pixbuf (AvatarFilePath); + else { + + // Let's try to get the person's gravatar for next time + WebClient WebClient = new WebClient (); + Uri GravatarUri = new Uri ("http://www.gravatar.com/avatar/" + GetMD5 (Email) + + ".jpg?s=" + Size + "&d=404"); + + string TmpFile = CombineMore ("/home/hbons/dsfdsf.jpg"); + + if (!File.Exists (TmpFile)) { + + WebClient.DownloadFileAsync (GravatarUri, TmpFile); + WebClient.DownloadFileCompleted += delegate { +// File.Delete (AvatarFilePath); + FileInfo TmpFileInfo = new FileInfo (TmpFile); + if (TmpFileInfo.Length > 255) + File.Move (TmpFile, AvatarFilePath); + }; + + } + + // Fall back to a generic icon if there is no gravatar + if (File.Exists (AvatarFilePath)) + return new Gdk.Pixbuf (AvatarFilePath); + else + return GetIcon ("avatar-default", Size); + + } + + } + // Quits the program private void Quit (object o, EventArgs args) { diff --git a/SparkleShare/Makefile.am b/SparkleShare/Makefile.am index 29ea226f..7dc70ae9 100644 --- a/SparkleShare/Makefile.am +++ b/SparkleShare/Makefile.am @@ -12,6 +12,7 @@ Defines.cs \ SparkleBubble.cs \ SparkleDialog.cs \ SparkleHelpers.cs \ +SparkleIntro.cs \ SparklePaths.cs \ SparklePlatform.cs \ SparkleRepo.cs \ diff --git a/SparkleShare/SparkleHelpers.cs b/SparkleShare/SparkleHelpers.cs index fbfb6301..d26bef19 100644 --- a/SparkleShare/SparkleHelpers.cs +++ b/SparkleShare/SparkleHelpers.cs @@ -34,18 +34,18 @@ namespace SparkleShare { } - // Get's the avatar for a specific email address and size + // Gets the avatar for a specific email address and size public static Gdk.Pixbuf GetAvatar (string Email, int Size) { - string AvatarPath = CombineMore (SparklePaths.SparkleAvatarPath, Size + "x" + Size); + string AvatarPath = CombineMore (SparklePaths.SparkleLocalIconPath, Size + "x" + Size, "status"); if (!Directory.Exists (AvatarPath)) { Directory.CreateDirectory (AvatarPath); SparkleHelpers.DebugInfo ("Config", "Created '" + AvatarPath + "'"); } - string AvatarFilePath = CombineMore (AvatarPath, Email); + string AvatarFilePath = CombineMore (AvatarPath, "avatar-" + Email); if (File.Exists (AvatarFilePath)) return new Gdk.Pixbuf (AvatarFilePath); @@ -85,9 +85,9 @@ namespace SparkleShare { public static string GetMD5 (string s) { MD5 md5 = new MD5CryptoServiceProvider (); - Byte[] Bytes = ASCIIEncoding.Default.GetBytes (s); - Byte[] EncodedBytes = md5.ComputeHash (Bytes); - return BitConverter.ToString (EncodedBytes).ToLower ().Replace ("-", ""); + Byte[] bytes = ASCIIEncoding.Default.GetBytes (s); + Byte[] encodedBytes = md5.ComputeHash (bytes); + return BitConverter.ToString (encodedBytes).ToLower ().Replace ("-", ""); } @@ -124,7 +124,8 @@ namespace SparkleShare { public static Gdk.Pixbuf GetIcon (string Name, int Size) { IconTheme IconTheme = new IconTheme (); - IconTheme.AppendSearchPath (CombineMore (SparklePaths.SparkleInstallPath, "icons")); + IconTheme.AppendSearchPath (SparklePaths.SparkleIconPath); + IconTheme.AppendSearchPath (SparklePaths.SparkleLocalIconPath); return IconTheme.LoadIcon (Name, Size, IconLookupFlags.GenericFallback); } diff --git a/SparkleShare/SparkleIntro.cs b/SparkleShare/SparkleIntro.cs new file mode 100644 index 00000000..853dd808 --- /dev/null +++ b/SparkleShare/SparkleIntro.cs @@ -0,0 +1,137 @@ +// SparkleShare, an instant update workflow to Git. +// Copyright (C) 2010 Hylke Bons +// +// 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 . + +using Gtk; +using Mono.Unix; +using SparkleShare; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text.RegularExpressions; +using System.Timers; + +namespace SparkleShare { + + public class SparkleIntro : Window + { + + // Short alias for the translations + public static string _ (string s) + { + return Catalog.GetString (s); + } + + public SparkleIntro () : base ("") + { + + BorderWidth = 0; + SetSizeRequest (600, 400); + Resizable = false; + IconName = "folder-sparkleshare"; + + WindowPosition = WindowPosition.Center; + + HBox layout_horizontal = new HBox (false, 6); + + Image side_splash = new Image ("/home/hbons/github/SparkleShare/data/side-splash.png"); + + layout_horizontal.PackStart (side_splash, false, false, 0); + + VBox wrapper = new VBox (false, 0); + + VBox layout_vertical = new VBox (false, 0); + + Label introduction = new Label ("Welcome to SparkleShare!"); + introduction.UseMarkup = true; + introduction.Xalign = 0; + + Label information = new Label ("Before we can create a SparkleShare folder on this \n" + + "computer, we need a few bits of information from you."); + information.Xalign = 0; + + + Entry name_entry = new Entry (""); + Label name_label = new Label (_("Full Name:")); + + UnixUserInfo unix_user_info = new UnixUserInfo (UnixEnvironment.UserName); + + name_entry.Text = unix_user_info.RealName; + name_label.UseMarkup = true; + name_label.Xalign = 0; + + + Table table = new Table (6, 2, true); + table.RowSpacing = 6; + + + Entry email_entry = new Entry (""); + Label email_label = new Label (_("Email:")); + email_label.UseMarkup = true; + email_label.Xalign = 0; + + Entry server_entry = new Entry ("ssh://gitorious.org/sparkleshare"); + Label server_label = new Label (_("Folder Address:")); + server_label.UseMarkup = true; + server_label.Xalign = 0; + server_label.Sensitive = false; + server_entry.Sensitive = false; + + CheckButton check_button = new CheckButton ("I already have an existing folder on a SparkleShare server"); + check_button.Clicked += delegate { + if (check_button.Active) { + server_label.Sensitive = true; + server_entry.Sensitive = true; + server_entry.HasFocus = true; + } else { + server_label.Sensitive = false; + server_entry.Sensitive = false; + } + ShowAll (); + }; + + table.Attach (name_label, 0, 1, 0, 1); + table.Attach (name_entry, 1, 2, 0, 1); + table.Attach (email_label, 0, 1, 1, 2); + table.Attach (email_entry, 1, 2, 1, 2); + table.Attach (check_button, 0, 2, 3, 4); + table.Attach (server_label, 0, 1, 4, 5); + table.Attach (server_entry, 1, 2, 4, 5); + + HButtonBox controls = new HButtonBox (); + controls.Layout = ButtonBoxStyle.End; + Button done_button = new Button ("Next"); + controls.Add (done_button); + + layout_vertical.PackStart (introduction, false, false, 0); + layout_vertical.PackStart (information, false, false, 21); + layout_vertical.PackStart (new Label (""), false, false, 0); + layout_vertical.PackStart (table, false, false, 0); + + wrapper.PackStart (layout_vertical, true, true, 0); + layout_vertical.BorderWidth = 30; + controls.BorderWidth = 12; + wrapper.PackStart (controls, false, true, 0); + layout_horizontal.PackStart (wrapper, true, true, 0); + + Add (layout_horizontal); + ShowAll (); + + } + + } + +} diff --git a/SparkleShare/SparklePaths.cs b/SparkleShare/SparklePaths.cs index 1db4f35d..3f291b26 100644 --- a/SparkleShare/SparklePaths.cs +++ b/SparkleShare/SparklePaths.cs @@ -26,32 +26,20 @@ namespace SparkleShare { private static UnixUserInfo UnixUserInfo = new UnixUserInfo (UnixEnvironment.UserName); public static string HomePath = UnixUserInfo.HomeDirectory; - + public static string SparklePath = Path.Combine (HomePath ,"SparkleShare"); public static string SparkleTmpPath = Path.Combine (SparklePath, ".tmp"); public static string SparkleConfigPath = SparkleHelpers.CombineMore (HomePath, ".config", "sparkleshare"); - - public static string SparkleInstallPath = SparkleHelpers.CombineMore (Defines.PREFIX, + + public static string SparkleInstallPath = SparkleHelpers.CombineMore (Defines.PREFIX, "sparkleshare"); + + public static string SparkleLocalIconPath = SparkleHelpers.CombineMore (HomePath, ".icons", "sparkleshare"); + + public static string SparkleIconPath = SparkleHelpers.CombineMore (Defines.PREFIX, "sparkleshare", "icons", "hicolor"); - public static string SparkleAvatarPath { - - get { - - string XDG_CACHE_HOME = Environment.GetEnvironmentVariable ("XDG_CACHE_HOME"); - - if (XDG_CACHE_HOME != null) - return Path.Combine (XDG_CACHE_HOME, "sparkleshare"); - else - return SparkleHelpers.CombineMore (HomePath, ".cache", "sparkleshare"); - } - - } - - public static string SparkleIconPath = SparkleHelpers.CombineMore ("usr", "share", "icons", "hicolor"); - } } diff --git a/SparkleShare/SparkleUI.cs b/SparkleShare/SparkleUI.cs index b71ad7f6..3293cfa5 100644 --- a/SparkleShare/SparkleUI.cs +++ b/SparkleShare/SparkleUI.cs @@ -137,7 +137,8 @@ namespace SparkleShare { // Don't create the window and status // icon when --disable-gui was given if (!HideUI) { - + SparkleIntro intro = new SparkleIntro (); + intro.ShowAll (); // Show a notification if there are no folders yet if (SparkleShare.Repositories.Length == 0) { @@ -193,7 +194,7 @@ namespace SparkleShare { // Create place to store configuration user's home folder string ConfigPath = SparklePaths.SparkleConfigPath; - string AvatarPath = SparklePaths.SparkleAvatarPath; + string LocalIconPath = SparklePaths.SparkleLocalIconPath; if (!Directory.Exists (ConfigPath)) { @@ -201,8 +202,8 @@ namespace SparkleShare { SparkleHelpers.DebugInfo ("Config", "Created '" + ConfigPath + "'"); // Create a place to store the avatars - Directory.CreateDirectory (AvatarPath); - SparkleHelpers.DebugInfo ("Config", "Created '" + AvatarPath + "'"); + Directory.CreateDirectory (LocalIconPath); + SparkleHelpers.DebugInfo ("Config", "Created '" + LocalIconPath + "'"); } diff --git a/SparkleShare/SparkleWindow.cs b/SparkleShare/SparkleWindow.cs index 7f28932d..5bcdc15e 100644 --- a/SparkleShare/SparkleWindow.cs +++ b/SparkleShare/SparkleWindow.cs @@ -48,7 +48,7 @@ namespace SparkleShare { BorderWidth = 12; // TRANSLATORS: {0} is a folder name, and {1} is a server address - Title = String.Format(_("‘{0}’ on {1}"), SparkleRepo.Name, + Title = String.Format(_("Recent Events in ‘{0}’ on {1}"), SparkleRepo.Name, SparkleRepo.RemoteOriginUrl); IconName = "folder"; @@ -232,6 +232,7 @@ namespace SparkleShare { } ScrolledWindow = new ScrolledWindow (); + ScrolledWindow.ShadowType = ShadowType.None; ScrolledWindow.AddWithViewport (layout_vertical); return ScrolledWindow; From b4611c88bd107097a01461ac639ac281e9a041ba Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Tue, 13 Jul 2010 20:04:45 +0100 Subject: [PATCH 10/37] [sparklediff] Fix crash caused by incorrect handling of avatar paths --- SparkleDiff/SparkleDiffWindow.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/SparkleDiff/SparkleDiffWindow.cs b/SparkleDiff/SparkleDiffWindow.cs index 248f27b4..bebf5c59 100644 --- a/SparkleDiff/SparkleDiffWindow.cs +++ b/SparkleDiff/SparkleDiffWindow.cs @@ -265,7 +265,7 @@ namespace SparkleShare { string HomePath = UnixUserInfo.HomeDirectory; - string SparkleLocalIconPath = CombineMore (HomePath + "/.icons", "sparkleshare"); + string SparkleLocalIconPath = CombineMore (HomePath, ".icons", "sparkleshare"); string AvatarPath = CombineMore (SparkleLocalIconPath, Size + "x" + Size, "status"); @@ -285,16 +285,17 @@ namespace SparkleShare { Uri GravatarUri = new Uri ("http://www.gravatar.com/avatar/" + GetMD5 (Email) + ".jpg?s=" + Size + "&d=404"); - string TmpFile = CombineMore ("/home/hbons/dsfdsf.jpg"); + string TmpFile = CombineMore (HomePath, "SparkleShare", ".tmp", Email + Size); if (!File.Exists (TmpFile)) { WebClient.DownloadFileAsync (GravatarUri, TmpFile); WebClient.DownloadFileCompleted += delegate { -// File.Delete (AvatarFilePath); + File.Delete (AvatarFilePath); FileInfo TmpFileInfo = new FileInfo (TmpFile); if (TmpFileInfo.Length > 255) File.Move (TmpFile, AvatarFilePath); + Console.WriteLine ("AAAAAAA"); }; } From f222439ed050a34257ddd43757dc4fd10e62d094 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Tue, 13 Jul 2010 20:19:11 +0100 Subject: [PATCH 11/37] [sparklediff] Show two different revisions by default --- SparkleDiff/RevisionView.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/SparkleDiff/RevisionView.cs b/SparkleDiff/RevisionView.cs index 328aa989..33b92caf 100644 --- a/SparkleDiff/RevisionView.cs +++ b/SparkleDiff/RevisionView.cs @@ -37,12 +37,12 @@ namespace SparkleShare { public ScrolledWindow ScrolledWindow; public IconView IconView; + public int Selected; private ToggleButton ToggleButton; private Viewport Viewport; private ListStore Store; private Image Image; - private int Selected; private int Count; @@ -208,7 +208,7 @@ namespace SparkleShare { { if (i > -1 && i <= Count) { - Selected = i; + Selected = i;Console.WriteLine ("selected: " + i); return true; } @@ -243,7 +243,7 @@ namespace SparkleShare { { // Select the second revision - SetSelected (1); + Selected = 1; // Take reading direction for time into account if (Direction == Gtk.TextDirection.Ltr) @@ -263,8 +263,6 @@ namespace SparkleShare { public RightRevisionView () : base () { - SetSelected (0); - // Take reading direction for time into account if (Direction == Gtk.TextDirection.Ltr) ScrolledWindow.Placement = CornerType.TopLeft; From f3d4dd588a59ed9ee8df6d89ba3f9ee1e35aaf67 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Wed, 14 Jul 2010 16:28:33 +0100 Subject: [PATCH 12/37] [sparklediff] Set focus on iconview if button is toggled --- SparkleDiff/RevisionView.cs | 3 ++- SparkleDiff/SparkleDiffWindow.cs | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/SparkleDiff/RevisionView.cs b/SparkleDiff/RevisionView.cs index 33b92caf..d84af6c9 100644 --- a/SparkleDiff/RevisionView.cs +++ b/SparkleDiff/RevisionView.cs @@ -171,6 +171,7 @@ namespace SparkleShare { TreePath path = new TreePath (GetSelected ().ToString()); IconView.ScrollToPath (path, (float) 0.5, (float) 0.5); + IconView.GrabFocus (); } else { @@ -208,7 +209,7 @@ namespace SparkleShare { { if (i > -1 && i <= Count) { - Selected = i;Console.WriteLine ("selected: " + i); + Selected = i; return true; } diff --git a/SparkleDiff/SparkleDiffWindow.cs b/SparkleDiff/SparkleDiffWindow.cs index bebf5c59..0d7b0b81 100644 --- a/SparkleDiff/SparkleDiffWindow.cs +++ b/SparkleDiff/SparkleDiffWindow.cs @@ -136,7 +136,7 @@ namespace SparkleShare { ResizeToViews (); // Order time view according to the user's reading direction - if (Direction == Gtk.TextDirection.Rtl) // See Deejay1? I can do i18n too! :P + if (Direction == Gtk.TextDirection.Rtl) layout_horizontal.ReorderChild (ViewLeft, 1); @@ -146,12 +146,12 @@ namespace SparkleShare { dialog_buttons.Layout = ButtonBoxStyle.End; dialog_buttons.BorderWidth = 0; - Button CloseButton = new Button (Stock.Close); - CloseButton.Clicked += delegate (object o, EventArgs args) { + Button close_button = new Button (Stock.Close); + close_button.Clicked += delegate (object o, EventArgs args) { Environment.Exit (0); }; - - dialog_buttons.Add (CloseButton); + + dialog_buttons.Add (close_button); layout_vertical.PackStart (layout_horizontal, true, true, 0); layout_vertical.PackStart (dialog_buttons, false, false, 0); From 28554164ddf4037c86b3c55f31e4e5eb781b6fe6 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Thu, 15 Jul 2010 12:22:32 +0100 Subject: [PATCH 13/37] [sparklediff] Use system colours for iconview and fix some bugs --- SparkleDiff/RevisionView.cs | 47 ++++++++++++++++----- SparkleDiff/SparkleDiffWindow.cs | 72 +++++++++++++++++++++----------- SparkleShare/SparkleDialog.cs | 28 ++++++++++++- SparkleShare/SparkleHelpers.cs | 51 +--------------------- SparkleShare/SparkleRepo.cs | 18 +++++++- 5 files changed, 129 insertions(+), 87 deletions(-) diff --git a/SparkleDiff/RevisionView.cs b/SparkleDiff/RevisionView.cs index d84af6c9..9b4be802 100644 --- a/SparkleDiff/RevisionView.cs +++ b/SparkleDiff/RevisionView.cs @@ -14,9 +14,6 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -// TODO: Hand cursor when hovering icon view items -// TODO: Use theme colours - using Gtk; using Mono.Unix; using System; @@ -39,11 +36,13 @@ namespace SparkleShare { public IconView IconView; public int Selected; - private ToggleButton ToggleButton; + public ToggleButton ToggleButton; private Viewport Viewport; private ListStore Store; private Image Image; private int Count; + private string SecondaryTextColor; + private string SelectedTextColor; public RevisionView () : base (false, 0) @@ -52,6 +51,12 @@ namespace SparkleShare { Count = 0; Selected = 0; + TreeView treeview = new TreeView (); + SelectedTextColor = GdkColorToHex (treeview.Style.Foreground (StateType.Selected)); + + Window window = new Window (""); + SecondaryTextColor = GdkColorToHex (window.Style.Foreground (StateType.Insensitive)); + ToggleButton = new ToggleButton (); ToggleButton.Clicked += ToggleView; ToggleButton.Relief = ReliefStyle.None; @@ -85,18 +90,37 @@ namespace SparkleShare { public void ChangeSelection (object o, EventArgs args) { + TreeIter iter; + Store.GetIter (out iter, new TreePath (GetSelected ().ToString ())); + string text = (string) Store.GetValue (iter, 1); + Store.SetValue (iter, 1, text.Replace (SelectedTextColor, SecondaryTextColor)); + if (IconView.SelectedItems.Length > 0) { - TreeIter iter; Store.GetIter (out iter, IconView.SelectedItems [0]); SetSelected ((int) Store.GetValue (iter, 2)); + text = (string) Store.GetValue (iter, 1); + text = text.Replace (SecondaryTextColor, SelectedTextColor); + Store.SetValue (iter, 1, text); + } else { IconView.SelectPath (new TreePath (GetSelected ().ToString())); } - + + } + + + // Converts a Gdk RGB color to a hex value. + // Example: from "rgb:0,0,0" to "#000000" + public string GdkColorToHex (Gdk.Color color) + { + return String.Format("#{0:X2}{1:X2}{2:X2}", + (int) Math.Truncate(color.Red / 256.00), + (int) Math.Truncate(color.Green / 256.00), + (int) Math.Truncate(color.Blue / 256.00)); } @@ -126,8 +150,8 @@ namespace SparkleShare { string text = (string) Store.GetValue (iter, 1); Gdk.Pixbuf pixbuf = (Gdk.Pixbuf) Store.GetValue (iter, 0); - - Label label = new Label (text); + + Label label = new Label (text.Replace (SelectedTextColor, SecondaryTextColor)); label.UseMarkup = true; Arrow arrow_down = new Arrow (ArrowType.Down, ShadowType.None); @@ -152,14 +176,17 @@ namespace SparkleShare { public void AddRow (Gdk.Pixbuf pixbuf, string header, string subtext) { - Store.AppendValues (pixbuf, "" + header + "\n" + subtext + "", Count); + Store.AppendValues (pixbuf, "" + header + "\n" + + "" + subtext + "", + Count); + IconView.Model = Store; Count++; } - // Toggles between a displayed image and a list of revisions + // Toggles between a displayed image and a list of revisions public void ToggleView (object o, EventArgs args) { diff --git a/SparkleDiff/SparkleDiffWindow.cs b/SparkleDiff/SparkleDiffWindow.cs index 0d7b0b81..d4a831fa 100644 --- a/SparkleDiff/SparkleDiffWindow.cs +++ b/SparkleDiff/SparkleDiffWindow.cs @@ -54,8 +54,7 @@ namespace SparkleShare { DeleteEvent += Quit; - // TRANSLATORS: The parameter is a filename - Title = String.Format(_("Comparing Revisions of ‘{0}’"), file_name); + Title = file_name; VBox layout_vertical = new VBox (false, 12); @@ -127,19 +126,30 @@ namespace SparkleShare { HookUpViews (); }; + + ViewLeft.ToggleButton.Clicked += delegate { + if (ViewLeft.ToggleButton.Active) + DetachViews (); + else + HookUpViews (); + }; + + ViewRight.ToggleButton.Clicked += delegate { + if (ViewLeft.ToggleButton.Active) + DetachViews (); + else + HookUpViews (); + }; layout_horizontal.PackStart (ViewLeft); layout_horizontal.PackStart (ViewRight); - - ResizeToViews (); // Order time view according to the user's reading direction if (Direction == Gtk.TextDirection.Rtl) layout_horizontal.ReorderChild (ViewLeft, 1); - HookUpViews (); HButtonBox dialog_buttons = new HButtonBox (); @@ -159,24 +169,9 @@ namespace SparkleShare { Add (layout_vertical); } - - // Converts a UNIX timestamp to a more usable time object - public DateTime UnixTimestampToDateTime (int timestamp) - { - DateTime unix_epoch = new DateTime (1970, 1, 1, 0, 0, 0, 0); - return unix_epoch.AddSeconds (timestamp); - } - - - // Looks up an icon from the system's theme - public Gdk.Pixbuf GetIcon (string name, int size) - { - IconTheme icon_theme = new IconTheme (); - icon_theme.AppendSearchPath (System.IO.Path.Combine ("/usr/share/sparkleshare", "icons")); - return icon_theme.LoadIcon (name, size, IconLookupFlags.GenericFallback); - } + // Resizes the window so it will fit the content in the best possible way private void ResizeToViews () { @@ -195,6 +190,7 @@ namespace SparkleShare { } + // Hooks up two views so their scrollbars will be kept in sync private void HookUpViews () { @@ -205,6 +201,18 @@ namespace SparkleShare { ViewRight.ScrolledWindow.Vadjustment.ValueChanged += SyncViewsVertically; } + + + // Detach the two views from each other so they don't try to sync anymore + private void DetachViews () + { + + ViewLeft.ScrolledWindow.Hadjustment.ValueChanged -= SyncViewsHorizontally; + ViewLeft.ScrolledWindow.Vadjustment.ValueChanged -= SyncViewsVertically; + ViewRight.ScrolledWindow.Hadjustment.ValueChanged -= SyncViewsHorizontally; + ViewRight.ScrolledWindow.Vadjustment.ValueChanged -= SyncViewsVertically; + + } // Keeps the two image views in sync horizontally @@ -244,6 +252,23 @@ namespace SparkleShare { } + // Converts a UNIX timestamp to a more usable time object + public DateTime UnixTimestampToDateTime (int timestamp) + { + DateTime unix_epoch = new DateTime (1970, 1, 1, 0, 0, 0, 0); + return unix_epoch.AddSeconds (timestamp); + } + + + // Looks up an icon from the system's theme + public Gdk.Pixbuf GetIcon (string name, int size) + { + IconTheme icon_theme = new IconTheme (); + icon_theme.AppendSearchPath (System.IO.Path.Combine ("/usr/share/sparkleshare", "icons")); + return icon_theme.LoadIcon (name, size, IconLookupFlags.GenericFallback); + } + + // Creates an MD5 hash of input public static string GetMD5 (string s) { @@ -255,12 +280,10 @@ namespace SparkleShare { // TODO: Turn this into an avatar fetching library - // TODO: This should be included from SparkleHelpers, but I don't know how to do that // Gets the avatar for a specific email address and size public Gdk.Pixbuf GetAvatar (string Email, int Size) { - UnixUserInfo UnixUserInfo = new UnixUserInfo (UnixEnvironment.UserName); string HomePath = UnixUserInfo.HomeDirectory; @@ -271,7 +294,6 @@ namespace SparkleShare { if (!Directory.Exists (AvatarPath)) { Directory.CreateDirectory (AvatarPath); -// SparkleHelpers.DebugInfo ("Config", "Created '" + AvatarPath + "'"); } string AvatarFilePath = CombineMore (AvatarPath, Email); @@ -295,7 +317,6 @@ namespace SparkleShare { FileInfo TmpFileInfo = new FileInfo (TmpFile); if (TmpFileInfo.Length > 255) File.Move (TmpFile, AvatarFilePath); - Console.WriteLine ("AAAAAAA"); }; } @@ -310,6 +331,7 @@ namespace SparkleShare { } + // Quits the program private void Quit (object o, EventArgs args) { diff --git a/SparkleShare/SparkleDialog.cs b/SparkleShare/SparkleDialog.cs index b3af6c27..e3017d3d 100644 --- a/SparkleShare/SparkleDialog.cs +++ b/SparkleShare/SparkleDialog.cs @@ -20,6 +20,7 @@ using SparkleShare; using System; using System.Diagnostics; using System.IO; +using System.Text.RegularExpressions; namespace SparkleShare { @@ -109,7 +110,7 @@ namespace SparkleShare { HideAll (); string RepoRemoteUrl = RemoteUrlCombo.Entry.Text; - RepoRemoteUrl = SparkleHelpers.SparkleToGitUrl (RepoRemoteUrl); + RepoRemoteUrl = SparkleToGitUrl (RepoRemoteUrl); int SlashPos = RepoRemoteUrl.LastIndexOf ("/"); int ColumnPos = RepoRemoteUrl.LastIndexOf (":"); @@ -212,12 +213,35 @@ namespace SparkleShare { // Enables the Add button when the fields are // filled in correctly public void CheckFields (object o, EventArgs args) { - if (SparkleHelpers.IsGitUrl (RemoteUrlCombo.Entry.Text)) + if (IsGitUrl (RemoteUrlCombo.Entry.Text)) AddButton.Sensitive = true; else AddButton.Sensitive = false; } + + // Convert the more human readable sparkle:// url to something Git can use. + // Example: sparkle://gitorious.org/sparkleshare ssh://git@gitorious.org/sparkleshare + public static string SparkleToGitUrl (string Url) + { + if (Url.StartsWith ("sparkle://")) + Url = Url.Replace ("sparkle://", "ssh://git@"); + + // Usually don't need the ".git" at the end. + // It looks ugly as a folder too. + if (Url.EndsWith (".git")) + Url = Url.Substring (0, Url.Length - 4); + + return Url; + } + + + // Checks if a url is a valid git url + public static bool IsGitUrl (string Url) + { + return Regex.Match (Url, @"(.)+(/|:)(.)+").Success; + } + } } diff --git a/SparkleShare/SparkleHelpers.cs b/SparkleShare/SparkleHelpers.cs index d26bef19..88f53e49 100644 --- a/SparkleShare/SparkleHelpers.cs +++ b/SparkleShare/SparkleHelpers.cs @@ -21,19 +21,13 @@ using System.IO; using System.Net; using System.Security.Cryptography; using System.Text; -using System.Text.RegularExpressions; + namespace SparkleShare { public static class SparkleHelpers { - public static string _ (string s) - { - return Catalog.GetString (s); - } - - // Gets the avatar for a specific email address and size public static Gdk.Pixbuf GetAvatar (string Email, int Size) { @@ -89,28 +83,9 @@ namespace SparkleShare { Byte[] encodedBytes = md5.ComputeHash (bytes); return BitConverter.ToString (encodedBytes).ToLower ().Replace ("-", ""); } - - - // Convert the more human readable sparkle:// url to - // something Git can use - // Example: sparkle://gitorious.org/sparkleshare - // to ssh://git@gitorious.org/sparkleshare - public static string SparkleToGitUrl (string Url) - { - if (Url.StartsWith ("sparkle://")) - Url = Url.Replace ("sparkle://", "ssh://git@"); - - // Usually don't need the ".git" at the end. - // It looks ugly as a folder too. - if (Url.EndsWith (".git")) - Url = Url.Substring (0, Url.Length - 4); - - return Url; - } - // Makes it possible to combine more than - // two paths at once. + // two paths at once public static string CombineMore (params string [] Parts) { string NewPath = " "; @@ -130,13 +105,6 @@ namespace SparkleShare { } - // Checks if a url is a valid git url - public static bool IsGitUrl (string Url) - { - return Regex.Match (Url, @"(.)+(/|:)(.)+").Success; - } - - public static bool ShowDebugInfo = true; @@ -198,21 +166,6 @@ namespace SparkleShare { } - // Checks for unicorns - public static void CheckForUnicorns (string s) { - - s = s.ToLower (); - if (s.Contains ("unicorn") && (s.Contains (".png") || s.Contains (".jpg"))) { - string title = _("Hold your ponies!"); - string subtext = _("SparkleShare is known to be insanely fast with \n" + - "pictures of unicorns. Please make sure your internets\n" + - "are upgraded to the latest version to avoid problems."); - SparkleBubble unicorn_bubble = new SparkleBubble (title, subtext); - unicorn_bubble.Show (); - } - - } - } } diff --git a/SparkleShare/SparkleRepo.cs b/SparkleShare/SparkleRepo.cs index 8d856d6a..291142ba 100644 --- a/SparkleShare/SparkleRepo.cs +++ b/SparkleShare/SparkleRepo.cs @@ -179,7 +179,7 @@ namespace SparkleShare { FetchTimer.Start (); BufferTimer.Start (); - SparkleHelpers.CheckForUnicorns (Message); + CheckForUnicorns (Message); } @@ -457,6 +457,22 @@ namespace SparkleShare { } + + // Checks for unicorns + public static void CheckForUnicorns (string s) { + + s = s.ToLower (); + if (s.Contains ("unicorn") && (s.Contains (".png") || s.Contains (".jpg"))) { + string title = _("Hold your ponies!"); + string subtext = _("SparkleShare is known to be insanely fast with \n" + + "pictures of unicorns. Please make sure your internets\n" + + "are upgraded to the latest version to avoid problems."); + SparkleBubble unicorn_bubble = new SparkleBubble (title, subtext); + unicorn_bubble.Show (); + } + + } + } } From 3db91fa9d9d93a1e802117ce582691410094dc6a Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Thu, 15 Jul 2010 20:39:12 +0100 Subject: [PATCH 14/37] Improve startup screen --- NotifySharp/NotifySharp.csproj | 42 ++++++++++++++- SparkleShare.sln | 11 ++-- SparkleShare/SparkleIntro.cs | 87 ++++++++++++++++++++++++++++++-- SparkleShare/SparkleShare.csproj | 4 +- SparkleShare/SparkleSpinner.cs | 16 +++--- 5 files changed, 142 insertions(+), 18 deletions(-) diff --git a/NotifySharp/NotifySharp.csproj b/NotifySharp/NotifySharp.csproj index 6053fdf9..4562b26f 100644 --- a/NotifySharp/NotifySharp.csproj +++ b/NotifySharp/NotifySharp.csproj @@ -1 +1,41 @@ - Debug AnyCPU 8.0.50727 2.0 {005CCA8E-DFBF-464A-B6DA-452C62D4589C} Library notifysharp notify-sharp true full false bin\Debug DEBUG prompt 4 none false bin\Release prompt 4 \ No newline at end of file + + + + Debug + AnyCPU + 8.0.50727 + 2.0 + {005CCA8E-DFBF-464A-B6DA-452C62D4589C} + Library + notifysharp + notify-sharp + + + true + full + false + bin\Debug + DEBUG + prompt + 4 + + + none + false + bin\Release + prompt + 4 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SparkleShare.sln b/SparkleShare.sln index e1d2563c..df6e25f5 100644 --- a/SparkleShare.sln +++ b/SparkleShare.sln @@ -5,6 +5,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SparkleShare", "SparkleShar EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NotifySharp", "NotifySharp\NotifySharp.csproj", "{005CCA8E-DFBF-464A-B6DA-452C62D4589C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpSSH", "SharpSSH\SharpSSH.csproj", "{BB50B7E2-4622-4D8B-B7FF-5E5D8F02D91F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -19,10 +21,13 @@ Global {728483AA-E34B-4441-BF2C-C8BC2901E4E0}.Debug|Any CPU.Build.0 = Debug|Any CPU {728483AA-E34B-4441-BF2C-C8BC2901E4E0}.Release|Any CPU.ActiveCfg = Release|Any CPU {728483AA-E34B-4441-BF2C-C8BC2901E4E0}.Release|Any CPU.Build.0 = Release|Any CPU + {BB50B7E2-4622-4D8B-B7FF-5E5D8F02D91F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BB50B7E2-4622-4D8B-B7FF-5E5D8F02D91F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BB50B7E2-4622-4D8B-B7FF-5E5D8F02D91F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BB50B7E2-4622-4D8B-B7FF-5E5D8F02D91F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution - name = SparkleShare - version = 0.1 StartupItem = SparkleShare\SparkleShare.csproj + name = SparkleShare EndGlobalSection -EndGlobal +EndGlobal diff --git a/SparkleShare/SparkleIntro.cs b/SparkleShare/SparkleIntro.cs index 853dd808..005e25a3 100644 --- a/SparkleShare/SparkleIntro.cs +++ b/SparkleShare/SparkleIntro.cs @@ -39,7 +39,7 @@ namespace SparkleShare { { BorderWidth = 0; - SetSizeRequest (600, 400); + SetSizeRequest (640, 400); Resizable = false; IconName = "folder-sparkleshare"; @@ -55,13 +55,14 @@ namespace SparkleShare { VBox layout_vertical = new VBox (false, 0); - Label introduction = new Label ("Welcome to SparkleShare!"); + Label introduction = new Label ("Welcome to SparkleShare!"); introduction.UseMarkup = true; introduction.Xalign = 0; - Label information = new Label ("Before we can create a SparkleShare folder on this \n" + + Label information = new Label ("Before we can create a SparkleShare folder on this " + "computer, we need a few bits of information from you."); information.Xalign = 0; + information.Wrap = true; Entry name_entry = new Entry (""); @@ -113,7 +114,21 @@ namespace SparkleShare { HButtonBox controls = new HButtonBox (); controls.Layout = ButtonBoxStyle.End; - Button done_button = new Button ("Next"); + + Button done_button = new Button (_("Next")); + + done_button.Clicked += delegate (object o, EventArgs args) { + done_button.Remove (done_button.Child); + HBox hbox = new HBox (); + hbox.Add (new SparkleSpinner ()); + hbox.Add (new Label ("Configuring…")); + done_button.Add (hbox); + done_button.Sensitive = false; + table.Sensitive = false; + done_button.ShowAll (); + ShowStepTwo (); + }; + controls.Add (done_button); layout_vertical.PackStart (introduction, false, false, 0); @@ -131,6 +146,70 @@ namespace SparkleShare { ShowAll (); } + + + public void ShowStepTwo () + { + + Remove (Child); + + HBox layout_horizontal = new HBox (false, 6); + + Image side_splash = new Image ("/home/hbons/github/SparkleShare/data/side-splash.png"); + + layout_horizontal.PackStart (side_splash, false, false, 0); + + VBox wrapper = new VBox (false, 0); + + VBox layout_vertical = new VBox (false, 0); + layout_vertical.BorderWidth = 30; + + Label introduction; + introduction = new Label ("SparkleShare ready to go!"); + + introduction.UseMarkup = true; + introduction.Xalign = 0; + + Label information; + information = new Label ("You can now start accepting invitations from others. " + + "Just click on invitations you get by email and " + + "we'll take care of the rest."); + + information.UseMarkup = true; + information.Wrap = true; + information.Xalign = 0; + + HBox link_wrapper = new HBox (false, 0); + LinkButton link = new LinkButton ("http://www.sparkleshare.org/", + _("Learn how to host your own SparkleSpace")); + + link_wrapper.PackStart (link, false, false, 0); + + layout_vertical.PackStart (introduction, false, false, 0); + layout_vertical.PackStart (information, false, false, 21); + layout_vertical.PackStart (link_wrapper, false, false, 0); + + HButtonBox controls = new HButtonBox (); + controls.Layout = ButtonBoxStyle.End; + controls.BorderWidth = 12; + + Button finish_button = new Button (_("Finish")); + + finish_button.Clicked += delegate (object o, EventArgs args) { + Destroy (); + }; + + controls.Add (finish_button); + + wrapper.PackStart (layout_vertical, true, true, 0); + wrapper.PackStart (controls, false, false, 0); + + layout_horizontal.Add (wrapper); + + Add (layout_horizontal); + ShowAll (); + + } } diff --git a/SparkleShare/SparkleShare.csproj b/SparkleShare/SparkleShare.csproj index 923cd71a..f42c092b 100644 --- a/SparkleShare/SparkleShare.csproj +++ b/SparkleShare/SparkleShare.csproj @@ -48,9 +48,9 @@ - + {005CCA8E-DFBF-464A-B6DA-452C62D4589C} - notify-sharp + NotifySharp diff --git a/SparkleShare/SparkleSpinner.cs b/SparkleShare/SparkleSpinner.cs index eb423366..e18d7e5d 100644 --- a/SparkleShare/SparkleSpinner.cs +++ b/SparkleShare/SparkleSpinner.cs @@ -37,20 +37,20 @@ namespace SparkleShare { CycleDuration = 750; CurrentStep = 0; - Size = 24; + Size = 16; - Gdk.Pixbuf SpinnerGallery = SparkleHelpers.GetIcon ("process-working", Size); + Gdk.Pixbuf spinner_gallery = SparkleHelpers.GetIcon ("process-working", Size); - int FramesInWidth = SpinnerGallery.Width / Size; - int FramesInHeight = SpinnerGallery.Height / Size; - NumSteps = FramesInWidth * FramesInHeight; + int frames_in_width = spinner_gallery.Width / Size; + int frames_in_height = spinner_gallery.Height / Size; + NumSteps = frames_in_width * frames_in_height; Images = new Gdk.Pixbuf [NumSteps - 1]; int i = 0; - for (int y = 0; y < FramesInHeight; y++) { - for (int x = 0; x < FramesInWidth; x++) { + for (int y = 0; y < frames_in_height; y++) { + for (int x = 0; x < frames_in_width; x++) { if (!(y == 0 && x == 0)) { - Images [i] = new Gdk.Pixbuf (SpinnerGallery, x * Size, y * Size, Size, Size); + Images [i] = new Gdk.Pixbuf (spinner_gallery, x * Size, y * Size, Size, Size); i++; } } From ba4889351a980b9f410bd6e5de71fcdbe5ab8297 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Thu, 15 Jul 2010 20:41:37 +0100 Subject: [PATCH 15/37] Bundle SharpSSH --- SharpSSH/AssemblyInfo.cs | 58 + SharpSSH/ExecTest.cs | 43 + SharpSSH/ITransferProtocol.cs | 61 + SharpSSH/Main.cs | 372 +++ SharpSSH/Scp.cs | 606 ++++ SharpSSH/Scp.old.cs | 354 +++ SharpSSH/SecureShell.cs | 17 + SharpSSH/Sftp.cs | 219 ++ SharpSSH/SharpSSH.csproj | 1 + SharpSSH/SharpSSH.csproj.user | 48 + SharpSSH/SharpSSH.mdp | 167 ++ SharpSSH/SshBase.cs | 335 +++ SharpSSH/SshExe.cs | 121 + SharpSSH/SshHelper.cs | 220 ++ SharpSSH/SshShell.cs | 256 ++ SharpSSH/SshStream.cs | 325 +++ SharpSSH/SshTransferException.cs | 44 + SharpSSH/SshTransferProtocolBase.cs | 114 + SharpSSH/Streams/CombinedStream.cs | 206 ++ SharpSSH/Streams/InputStream.cs | 69 + SharpSSH/Streams/OutputStream.cs | 70 + SharpSSH/Streams/PipedInputStream.cs | 517 ++++ SharpSSH/Streams/PipedOutputStream.cs | 276 ++ SharpSSH/Streams/PipedStream.cs | 97 + SharpSSH/Streams/ProtectedConsoleStream.cs | 174 ++ SharpSSH/java/Exception.cs | 22 + SharpSSH/java/Platform.cs | 18 + SharpSSH/java/RuntimeException.cs | 17 + SharpSSH/java/String.cs | 177 ++ SharpSSH/java/System.cs | 43 + SharpSSH/java/io/File.cs | 71 + SharpSSH/java/io/FileInputStream.cs | 45 + SharpSSH/java/io/FileOutputStream.cs | 60 + SharpSSH/java/io/InputStream.cs | 110 + SharpSSH/java/io/InputStreamWrapper.cs | 21 + SharpSSH/java/io/JStream.cs | 156 ++ SharpSSH/java/io/OutputStream.cs | 85 + SharpSSH/java/lang/Class.cs | 28 + SharpSSH/java/lang/Integer.cs | 24 + SharpSSH/java/lang/Runnable.cs | 12 + SharpSSH/java/lang/StringBuffer.cs | 64 + SharpSSH/java/lang/Thread.cs | 75 + SharpSSH/java/net/InetAddress.cs | 61 + SharpSSH/java/net/ServerSocket.cs | 27 + SharpSSH/java/net/Socket.cs | 95 + SharpSSH/java/util/Arrays.cs | 19 + SharpSSH/java/util/Enumeration.cs | 31 + SharpSSH/java/util/Hashtable.cs | 43 + SharpSSH/java/util/JavaString.cs | 27 + SharpSSH/java/util/Vector.cs | 57 + SharpSSH/jsch/Buffer.cs | 262 ++ SharpSSH/jsch/Channel.cs | 547 ++++ SharpSSH/jsch/Channel.old.cs | 472 ++++ SharpSSH/jsch/ChannelDirectTCPIP.cs | 213 ++ SharpSSH/jsch/ChannelExec.cs | 98 + SharpSSH/jsch/ChannelForwardedTCPIP.cs | 310 ++ SharpSSH/jsch/ChannelSession.cs | 96 + SharpSSH/jsch/ChannelSftp.cs | 2651 ++++++++++++++++++ SharpSSH/jsch/ChannelSftpStreamGet.cs | 157 ++ SharpSSH/jsch/ChannelSftpStreamPut.cs | 116 + SharpSSH/jsch/ChannelShell.cs | 89 + SharpSSH/jsch/ChannelSubsystem.cs | 89 + SharpSSH/jsch/ChannelX11.cs | 257 ++ SharpSSH/jsch/Cipher.cs | 43 + SharpSSH/jsch/Compression.cs | 42 + SharpSSH/jsch/DH.cs | 43 + SharpSSH/jsch/DHG1.cs | 314 +++ SharpSSH/jsch/DHGEX.cs | 340 +++ SharpSSH/jsch/ForwardedTCPIPDaemon.cs | 11 + SharpSSH/jsch/HASH.cs | 41 + SharpSSH/jsch/HostKey.cs | 74 + SharpSSH/jsch/HostKeyRepository.cs | 48 + SharpSSH/jsch/IO.cs | 183 ++ SharpSSH/jsch/Identity.cs | 44 + SharpSSH/jsch/IdentityFile.cs | 949 +++++++ SharpSSH/jsch/JSch.cs | 241 ++ SharpSSH/jsch/JSchAuthCancelException.cs | 21 + SharpSSH/jsch/JSchException.cs | 21 + SharpSSH/jsch/JSchPartialAuthException.cs | 26 + SharpSSH/jsch/KeyExchange.cs | 170 ++ SharpSSH/jsch/KeyPair.cs | 738 +++++ SharpSSH/jsch/KeyPairDSA.cs | 246 ++ SharpSSH/jsch/KeyPairGenDSA.cs | 44 + SharpSSH/jsch/KeyPairGenRSA.cs | 48 + SharpSSH/jsch/KeyPairRSA.cs | 352 +++ SharpSSH/jsch/KnownHosts.cs | 523 ++++ SharpSSH/jsch/MAC.cs | 44 + SharpSSH/jsch/Packet.cs | 138 + SharpSSH/jsch/PortWatcher.cs | 214 ++ SharpSSH/jsch/Proxy.cs | 43 + SharpSSH/jsch/ProxyHTTP.cs | 183 ++ SharpSSH/jsch/Random.cs | 38 + SharpSSH/jsch/Request.cs | 39 + SharpSSH/jsch/RequestExec.cs | 62 + SharpSSH/jsch/RequestPtyReq.cs | 60 + SharpSSH/jsch/RequestSftp.cs | 73 + SharpSSH/jsch/RequestShell.cs | 56 + SharpSSH/jsch/RequestSignal.cs | 54 + SharpSSH/jsch/RequestSubsystem.cs | 83 + SharpSSH/jsch/RequestWindowChange.cs | 74 + SharpSSH/jsch/RequestX11.cs | 67 + SharpSSH/jsch/ServerSocketFactory.cs | 13 + SharpSSH/jsch/Session.cs | 1792 ++++++++++++ SharpSSH/jsch/SftpATTRS.cs | 311 ++ SharpSSH/jsch/SftpException.cs | 19 + SharpSSH/jsch/SftpProgressMonitor.cs | 42 + SharpSSH/jsch/SignatureDSA.cs | 44 + SharpSSH/jsch/SignatureRSA.cs | 44 + SharpSSH/jsch/SocketFactory.cs | 42 + SharpSSH/jsch/UIKeyboardInteractive.cs | 42 + SharpSSH/jsch/UserAuth.cs | 57 + SharpSSH/jsch/UserAuthKeyboardInteractive.cs | 202 ++ SharpSSH/jsch/UserAuthNone.cs | 127 + SharpSSH/jsch/UserAuthPassword.cs | 145 + SharpSSH/jsch/UserAuthPublicKey.cs | 265 ++ SharpSSH/jsch/UserInfo.cs | 43 + SharpSSH/jsch/Util.cs | 580 ++++ SharpSSH/jsch/jce/AES128CBC.cs | 96 + SharpSSH/jsch/jce/BlowfishCBC.cs | 17 + SharpSSH/jsch/jce/DH.cs | 76 + SharpSSH/jsch/jce/HMACMD5.cs | 88 + SharpSSH/jsch/jce/HMACMD596.cs | 82 + SharpSSH/jsch/jce/HMACSHA1.cs | 88 + SharpSSH/jsch/jce/HMACSHA196.cs | 85 + SharpSSH/jsch/jce/KeyPairGenDSA.cs | 74 + SharpSSH/jsch/jce/KeyPairGenRSA.cs | 93 + SharpSSH/jsch/jce/MD5.cs | 69 + SharpSSH/jsch/jce/Random.cs | 75 + SharpSSH/jsch/jce/SHA1.cs | 66 + SharpSSH/jsch/jce/SignatureDSA.cs | 117 + SharpSSH/jsch/jce/SignatureRSA.cs | 157 ++ SharpSSH/jsch/jce/TripleDESCBC.cs | 104 + SharpSSH/lib/DiffieHellman.dll | Bin 0 -> 53248 bytes SharpSSH/lib/Org.Mentalis.Security.dll | Bin 0 -> 184320 bytes 134 files changed, 22360 insertions(+) create mode 100644 SharpSSH/AssemblyInfo.cs create mode 100644 SharpSSH/ExecTest.cs create mode 100644 SharpSSH/ITransferProtocol.cs create mode 100644 SharpSSH/Main.cs create mode 100644 SharpSSH/Scp.cs create mode 100644 SharpSSH/Scp.old.cs create mode 100644 SharpSSH/SecureShell.cs create mode 100644 SharpSSH/Sftp.cs create mode 100644 SharpSSH/SharpSSH.csproj create mode 100644 SharpSSH/SharpSSH.csproj.user create mode 100644 SharpSSH/SharpSSH.mdp create mode 100644 SharpSSH/SshBase.cs create mode 100644 SharpSSH/SshExe.cs create mode 100644 SharpSSH/SshHelper.cs create mode 100644 SharpSSH/SshShell.cs create mode 100644 SharpSSH/SshStream.cs create mode 100644 SharpSSH/SshTransferException.cs create mode 100644 SharpSSH/SshTransferProtocolBase.cs create mode 100644 SharpSSH/Streams/CombinedStream.cs create mode 100644 SharpSSH/Streams/InputStream.cs create mode 100644 SharpSSH/Streams/OutputStream.cs create mode 100644 SharpSSH/Streams/PipedInputStream.cs create mode 100644 SharpSSH/Streams/PipedOutputStream.cs create mode 100644 SharpSSH/Streams/PipedStream.cs create mode 100644 SharpSSH/Streams/ProtectedConsoleStream.cs create mode 100644 SharpSSH/java/Exception.cs create mode 100644 SharpSSH/java/Platform.cs create mode 100644 SharpSSH/java/RuntimeException.cs create mode 100644 SharpSSH/java/String.cs create mode 100644 SharpSSH/java/System.cs create mode 100644 SharpSSH/java/io/File.cs create mode 100644 SharpSSH/java/io/FileInputStream.cs create mode 100644 SharpSSH/java/io/FileOutputStream.cs create mode 100644 SharpSSH/java/io/InputStream.cs create mode 100644 SharpSSH/java/io/InputStreamWrapper.cs create mode 100644 SharpSSH/java/io/JStream.cs create mode 100644 SharpSSH/java/io/OutputStream.cs create mode 100644 SharpSSH/java/lang/Class.cs create mode 100644 SharpSSH/java/lang/Integer.cs create mode 100644 SharpSSH/java/lang/Runnable.cs create mode 100644 SharpSSH/java/lang/StringBuffer.cs create mode 100644 SharpSSH/java/lang/Thread.cs create mode 100644 SharpSSH/java/net/InetAddress.cs create mode 100644 SharpSSH/java/net/ServerSocket.cs create mode 100644 SharpSSH/java/net/Socket.cs create mode 100644 SharpSSH/java/util/Arrays.cs create mode 100644 SharpSSH/java/util/Enumeration.cs create mode 100644 SharpSSH/java/util/Hashtable.cs create mode 100644 SharpSSH/java/util/JavaString.cs create mode 100644 SharpSSH/java/util/Vector.cs create mode 100644 SharpSSH/jsch/Buffer.cs create mode 100644 SharpSSH/jsch/Channel.cs create mode 100644 SharpSSH/jsch/Channel.old.cs create mode 100644 SharpSSH/jsch/ChannelDirectTCPIP.cs create mode 100644 SharpSSH/jsch/ChannelExec.cs create mode 100644 SharpSSH/jsch/ChannelForwardedTCPIP.cs create mode 100644 SharpSSH/jsch/ChannelSession.cs create mode 100644 SharpSSH/jsch/ChannelSftp.cs create mode 100644 SharpSSH/jsch/ChannelSftpStreamGet.cs create mode 100644 SharpSSH/jsch/ChannelSftpStreamPut.cs create mode 100644 SharpSSH/jsch/ChannelShell.cs create mode 100644 SharpSSH/jsch/ChannelSubsystem.cs create mode 100644 SharpSSH/jsch/ChannelX11.cs create mode 100644 SharpSSH/jsch/Cipher.cs create mode 100644 SharpSSH/jsch/Compression.cs create mode 100644 SharpSSH/jsch/DH.cs create mode 100644 SharpSSH/jsch/DHG1.cs create mode 100644 SharpSSH/jsch/DHGEX.cs create mode 100644 SharpSSH/jsch/ForwardedTCPIPDaemon.cs create mode 100644 SharpSSH/jsch/HASH.cs create mode 100644 SharpSSH/jsch/HostKey.cs create mode 100644 SharpSSH/jsch/HostKeyRepository.cs create mode 100644 SharpSSH/jsch/IO.cs create mode 100644 SharpSSH/jsch/Identity.cs create mode 100644 SharpSSH/jsch/IdentityFile.cs create mode 100644 SharpSSH/jsch/JSch.cs create mode 100644 SharpSSH/jsch/JSchAuthCancelException.cs create mode 100644 SharpSSH/jsch/JSchException.cs create mode 100644 SharpSSH/jsch/JSchPartialAuthException.cs create mode 100644 SharpSSH/jsch/KeyExchange.cs create mode 100644 SharpSSH/jsch/KeyPair.cs create mode 100644 SharpSSH/jsch/KeyPairDSA.cs create mode 100644 SharpSSH/jsch/KeyPairGenDSA.cs create mode 100644 SharpSSH/jsch/KeyPairGenRSA.cs create mode 100644 SharpSSH/jsch/KeyPairRSA.cs create mode 100644 SharpSSH/jsch/KnownHosts.cs create mode 100644 SharpSSH/jsch/MAC.cs create mode 100644 SharpSSH/jsch/Packet.cs create mode 100644 SharpSSH/jsch/PortWatcher.cs create mode 100644 SharpSSH/jsch/Proxy.cs create mode 100644 SharpSSH/jsch/ProxyHTTP.cs create mode 100644 SharpSSH/jsch/Random.cs create mode 100644 SharpSSH/jsch/Request.cs create mode 100644 SharpSSH/jsch/RequestExec.cs create mode 100644 SharpSSH/jsch/RequestPtyReq.cs create mode 100644 SharpSSH/jsch/RequestSftp.cs create mode 100644 SharpSSH/jsch/RequestShell.cs create mode 100644 SharpSSH/jsch/RequestSignal.cs create mode 100644 SharpSSH/jsch/RequestSubsystem.cs create mode 100644 SharpSSH/jsch/RequestWindowChange.cs create mode 100644 SharpSSH/jsch/RequestX11.cs create mode 100644 SharpSSH/jsch/ServerSocketFactory.cs create mode 100644 SharpSSH/jsch/Session.cs create mode 100644 SharpSSH/jsch/SftpATTRS.cs create mode 100644 SharpSSH/jsch/SftpException.cs create mode 100644 SharpSSH/jsch/SftpProgressMonitor.cs create mode 100644 SharpSSH/jsch/SignatureDSA.cs create mode 100644 SharpSSH/jsch/SignatureRSA.cs create mode 100644 SharpSSH/jsch/SocketFactory.cs create mode 100644 SharpSSH/jsch/UIKeyboardInteractive.cs create mode 100644 SharpSSH/jsch/UserAuth.cs create mode 100644 SharpSSH/jsch/UserAuthKeyboardInteractive.cs create mode 100644 SharpSSH/jsch/UserAuthNone.cs create mode 100644 SharpSSH/jsch/UserAuthPassword.cs create mode 100644 SharpSSH/jsch/UserAuthPublicKey.cs create mode 100644 SharpSSH/jsch/UserInfo.cs create mode 100644 SharpSSH/jsch/Util.cs create mode 100644 SharpSSH/jsch/jce/AES128CBC.cs create mode 100644 SharpSSH/jsch/jce/BlowfishCBC.cs create mode 100644 SharpSSH/jsch/jce/DH.cs create mode 100644 SharpSSH/jsch/jce/HMACMD5.cs create mode 100644 SharpSSH/jsch/jce/HMACMD596.cs create mode 100644 SharpSSH/jsch/jce/HMACSHA1.cs create mode 100644 SharpSSH/jsch/jce/HMACSHA196.cs create mode 100644 SharpSSH/jsch/jce/KeyPairGenDSA.cs create mode 100644 SharpSSH/jsch/jce/KeyPairGenRSA.cs create mode 100644 SharpSSH/jsch/jce/MD5.cs create mode 100644 SharpSSH/jsch/jce/Random.cs create mode 100644 SharpSSH/jsch/jce/SHA1.cs create mode 100644 SharpSSH/jsch/jce/SignatureDSA.cs create mode 100644 SharpSSH/jsch/jce/SignatureRSA.cs create mode 100644 SharpSSH/jsch/jce/TripleDESCBC.cs create mode 100644 SharpSSH/lib/DiffieHellman.dll create mode 100644 SharpSSH/lib/Org.Mentalis.Security.dll diff --git a/SharpSSH/AssemblyInfo.cs b/SharpSSH/AssemblyInfo.cs new file mode 100644 index 00000000..742c8ce0 --- /dev/null +++ b/SharpSSH/AssemblyInfo.cs @@ -0,0 +1,58 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +// +[assembly: AssemblyTitle("SharpSSH")] +[assembly: AssemblyDescription("SSH library for .NET")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("tamirgal.com")] +[assembly: AssemblyProduct("SharpSSH")] +[assembly: AssemblyCopyright("Tamir Gal (c) 2007 and jcraft.com")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: + +[assembly: AssemblyVersion("1.1.1.13")] + +// +// In order to sign your assembly you must specify a key to use. Refer to the +// Microsoft .NET Framework documentation for more information on assembly signing. +// +// Use the attributes below to control which key is used for signing. +// +// Notes: +// (*) If no key is specified, the assembly is not signed. +// (*) KeyName refers to a key that has been installed in the Crypto Service +// Provider (CSP) on your machine. KeyFile refers to a file which contains +// a key. +// (*) If the KeyFile and the KeyName values are both specified, the +// following processing occurs: +// (1) If the KeyName can be found in the CSP, that key is used. +// (2) If the KeyName does not exist and the KeyFile does exist, the key +// in the KeyFile is installed into the CSP and used. +// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility. +// When specifying the KeyFile, the location of the KeyFile should be +// relative to the project output directory which is +// %Project Directory%\obj\. For example, if your KeyFile is +// located in the project directory, you would specify the AssemblyKeyFile +// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")] +// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework +// documentation for more information on this. +// +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] +[assembly: AssemblyKeyName("")] diff --git a/SharpSSH/ExecTest.cs b/SharpSSH/ExecTest.cs new file mode 100644 index 00000000..eca57b38 --- /dev/null +++ b/SharpSSH/ExecTest.cs @@ -0,0 +1,43 @@ +using System; +using Tamir.SharpSsh.jsch; +using System.Collections; +using System.IO; + +namespace Tamir.SharpSsh +{ + /// + /// Summary description for ExecTest. + /// + public class ExecTest + { + public static void Run() + { + JSch jsch=new JSch(); + Session session=jsch.getSession("root", "rhmanage", 22); + session.setPassword( "cisco" ); + + Hashtable config=new Hashtable(); + config.Add("StrictHostKeyChecking", "no"); + session.setConfig(config); + + session.connect(); + + Channel channel=session.openChannel("exec"); + ((ChannelExec)channel).setCommand("ifconfig"); + + StreamReader sr = new StreamReader( channel.getInputStream() ); + + channel.connect(); + + string line; + + while( (line=sr.ReadLine()) != null ) + { + Console.WriteLine( line ); + } + + channel.disconnect(); + session.disconnect(); + } + } +} diff --git a/SharpSSH/ITransferProtocol.cs b/SharpSSH/ITransferProtocol.cs new file mode 100644 index 00000000..6987c21a --- /dev/null +++ b/SharpSSH/ITransferProtocol.cs @@ -0,0 +1,61 @@ +using System; + +/* + * ITransferProtocol.cs + * + * Copyright (c) 2006 Tamir Gal, http://www.tamirgal.com, All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the distribution. + * + * 3. The names of the authors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR + * *OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + **/ +namespace Tamir.SharpSsh +{ + /// + /// Summary description for ITransferProtocol. + /// + public interface ITransferProtocol + { + void Connect(); + void Close(); + void Cancel(); + void Get(string fromFilePath, string toFilePath); + void Put(string fromFilePath, string toFilePath); + void Mkdir(string directory); + /// + /// Triggered when protocol transfer is starting + /// + event FileTransferEvent OnTransferStart; + /// + /// Triggered when protocol transfer ends + /// + event FileTransferEvent OnTransferEnd; + /// + /// Triggered on every interval with the transfer progress iformation. + /// + event FileTransferEvent OnTransferProgress; + } + + public delegate void FileTransferEvent(string src, string dst, int transferredBytes, int totalBytes, string message); +} diff --git a/SharpSSH/Main.cs b/SharpSSH/Main.cs new file mode 100644 index 00000000..446d51e2 --- /dev/null +++ b/SharpSSH/Main.cs @@ -0,0 +1,372 @@ +using System; +using System.IO; +using Tamir.Streams; +using Tamir.SharpSsh.jsch; +using Tamir.SharpSsh.jsch.examples; + +namespace Tamir +{ + /// + /// Summary description for Main. + /// + public class MainClass + { + public static void Main() + { + Console.WriteLine("sharpSsh 1.0"); + + //testSsh(); + //test(); + //testSig(); + //testSig2(); + //testSigFromJava(); + //testDump(); + //testDumpBase64(); + //testHMacMD5(); + testExamples(); + //jarAndScp(); + + + } + public static void test() + { + JSch jsch = new JSch(); + DH dh1 = null; + DH dh2 = null; + try + { + Type t=Type.GetType(jsch.getConfig("dh")); + dh1=(DH)(Activator.CreateInstance(t)); + dh1.init(); + dh2=(DH)(Activator.CreateInstance(t)); + dh2.init(); + } + catch(Exception ee) + { + Console.WriteLine(ee); + } + + dh1.setP(DHG1.p); + dh1.setG(DHG1.g); + dh2.setP(DHG1.p); + dh2.setG(DHG1.g); + + // The client responds with: + // byte SSH_MSG_KEXDH_INIT(30) + // mpint e <- g^x mod p + // x is a random number (1 < x < (p-1)/2) + + byte[] e=dh1.getE(); + byte[] f=dh2.getE(); + Console.WriteLine("Private1 = {0}", hex(e)); + Console.WriteLine(); + Console.WriteLine("Private2 = {0}", hex(f)); + Console.WriteLine(); + dh1.setF(f); + dh2.setF(e); + byte[] k1 = dh1.getK(); + byte[] k2 = dh2.getK(); + Console.WriteLine("Public1 = {0}", hex(k1)); + Console.WriteLine(); + Console.WriteLine("Public2 = {0}", hex(k2)); + Console.WriteLine(); + } + + public static void testSig() + { + byte[] hash = Util.getBytes( "Tamir" ); + Tamir.SharpSsh.jsch.jce.SignatureRSA enc_rsa = new Tamir.SharpSsh.jsch.jce.SignatureRSA(); + Tamir.SharpSsh.jsch.jce.SignatureRSA dec_rsa = new Tamir.SharpSsh.jsch.jce.SignatureRSA(); + Tamir.SharpSsh.jsch.jce.KeyPairGenRSA gen = new Tamir.SharpSsh.jsch.jce.KeyPairGenRSA(); + gen.init(512); + + enc_rsa.init(); + enc_rsa.setPrvKey(gen.KeyInfo); + enc_rsa.update(hash); + byte[] sig = enc_rsa.sign(); + + dump(gen.getE(), gen.getN(), sig, hash); + + dec_rsa.init(); + dec_rsa.setPubKey(gen.getE(), gen.getN()); + dec_rsa.update(hash); + + + + Console.WriteLine( dec_rsa.verify(sig) ); + } + + public static void testSigFromJava() + { + FileStream fs = File.OpenRead("e.bin"); + byte[] e = new byte[fs.Length]; + fs.Read(e, 0, e.Length); + fs.Close(); + + fs = File.OpenRead("n.bin"); + byte[] n = new byte[fs.Length]; + fs.Read(n, 0, n.Length); + fs.Close(); + + fs = File.OpenRead("sig.bin"); + byte[] sig = new byte[fs.Length]; + fs.Read(sig, 0, sig.Length); + fs.Close(); + + Console.Write("E: "); + Console.WriteLine( hex(e) ); + Console.Write("N: "); + Console.WriteLine( hex(n) ); + Console.Write("SIG: "); + Console.WriteLine( hex(sig) ); + + byte[] hash = Util.getBytes( "Tamir" ); + Tamir.SharpSsh.jsch.jce.SignatureRSA dec_rsa = new Tamir.SharpSsh.jsch.jce.SignatureRSA(); + dec_rsa.init(); + dec_rsa.setPubKey(e, n); + dec_rsa.update(hash); + Console.WriteLine( dec_rsa.verify(sig) ); + + + } + + public static void dump(byte[] e, byte[] n, byte[] sig, byte[] hash) + { + String fname = "e.bin"; + if(File.Exists(fname)) File.Delete(fname); + FileStream fs = File.OpenWrite(fname); + fs.Write(e, 0, e.Length); + fs.Close(); + + + fname = "n.bin"; + if(File.Exists(fname)) File.Delete(fname); + fs = File.OpenWrite(fname); + fs.Write(n, 0, n.Length); + fs.Close(); + + + fname = "sig.bin"; + if(File.Exists(fname)) File.Delete(fname); + fs = File.OpenWrite(fname); + fs.Write(sig, 0, sig.Length); + fs.Close(); + + fname = "hash.bin"; + if(File.Exists(fname)) File.Delete(fname); + fs = File.OpenWrite(fname); + fs.Write(hash, 0, hash.Length); + fs.Close(); + + + Console.Write("E: "); + Console.WriteLine( hex(e) ); + Console.WriteLine(); + Console.Write("N: "); + Console.WriteLine( hex(n) ); + Console.WriteLine(); + Console.Write("SIG: "); + Console.WriteLine( hex(sig) ); + Console.WriteLine(); + Console.Write("HASH: "); + Console.WriteLine( hex(hash) ); + Console.WriteLine(); + } + + public static void testDump() + { + String fname = "e.bin"; + FileStream fs = File.OpenRead(fname); + byte[] e = new byte[fs.Length]; + fs.Read(e, 0, e.Length); + fs.Close(); + + fname = "n.bin"; + fs = File.OpenRead(fname); + byte[] n = new byte[fs.Length]; + fs.Read(n, 0, n.Length); + fs.Close(); + + fname = "sig.bin"; + fs = File.OpenRead(fname); + byte[] sig = new byte[fs.Length]; + fs.Read(sig, 0, sig.Length); + fs.Close(); + + fname = "hash.bin"; + fs = File.OpenRead(fname); + byte[] hash = new byte[fs.Length]; + fs.Read(hash, 0, hash.Length); + fs.Close(); + + print("E", e); + print("N", n); + print("SIG", sig); + print("HASH", hash); + + Tamir.SharpSsh.jsch.jce.SignatureRSA dec_rsa = new Tamir.SharpSsh.jsch.jce.SignatureRSA(); + dec_rsa.init(); + dec_rsa.setPubKey(e, n); + dec_rsa.update(hash); + + Console.WriteLine( dec_rsa.verify(sig) ); + Console.WriteLine(); + } + + public static void testDumpBase64() + { + String fname = "e.bin"; + StreamReader fs = File.OpenText(fname); + string base64 = fs.ReadToEnd(); + byte[] e = Convert.FromBase64String( base64 ); + fs.Close(); + + fname = "n.bin"; + fs = File.OpenText(fname); + base64 = fs.ReadToEnd(); + byte[] n = Convert.FromBase64String( base64 ); + fs.Close(); + + fname = "sig.bin"; + fs = File.OpenText(fname); + base64 = fs.ReadToEnd(); + byte[] sig = Convert.FromBase64String( base64 ); + fs.Close(); + + fname = "hash.bin"; + fs = File.OpenText(fname); + base64 = fs.ReadToEnd(); + byte[] hash = Convert.FromBase64String( base64 ); + fs.Close(); + + print("E", e); + print("N", n); + print("SIG", sig); + print("HASH", hash); + + Tamir.SharpSsh.jsch.jce.SignatureRSA dec_rsa = new Tamir.SharpSsh.jsch.jce.SignatureRSA(); + dec_rsa.init(); + dec_rsa.setPubKey(e, n); + dec_rsa.update(hash); + + Console.WriteLine( dec_rsa.verify(sig) ); + Console.WriteLine(); + } + + public static void testHMacMD5() + { + byte[] msg = Util.getBytes("Tamir"); + byte[] key = new byte[]{1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6}; + Tamir.SharpSsh.jsch.jce.HMACMD5 md5 = new Tamir.SharpSsh.jsch.jce.HMACMD5(); + md5.init( key ); + md5.update(msg, 0, msg.Length); + byte[] hash = md5.doFinal(); + Console.WriteLine(hex(hash)); + } + + public static void testExamples() + { + //String[] arg = new string[]{"rsa", "key", "sharpSSH"}; + //KeyGen.RunExample(arg); + +// String[] arg = new string[]{"key"}; +// ChangePassphrase.RunExample(arg); + + //UserAuthPubKey.RunExample(null); + + Tamir.SharpSsh.jsch.examples.KnownHosts.RunExample(null); + +// String[] arg = new string[]{"C:\\untitled.db", "root@rhclient8:/root/mng/tamir/file"}; +// Tamir.SharpSsh.jsch.examples.ScpTo.RunExample(arg); + +// String[] arg = new string[]{"root@rhclient8:/root/mng/tamir/file", "file.txt"}; +// Tamir.SharpSsh.jsch.examples.ScpFrom.RunExample(arg); + +// Tamir.SharpSsh.jsch.examples.Sftp.RunExample(null); + } + + public static void jarAndScp() + { + TextReader r = File.OpenText( "jarAndScp.txt" ); + string dir = r.ReadLine(); + string host = r.ReadLine(); + string path = r.ReadLine(); + string user = r.ReadLine(); + string pass = r.ReadLine(); + r.Close(); + string file = dir+".jar"; + string jarFile = "\""+dir+".jar\""; + //dir = "\""+dir+"\""; + System.Diagnostics.ProcessStartInfo p = new System.Diagnostics.ProcessStartInfo(@"D:\Program Files\Java\jdk1.5.0_03\bin\jar.exe"); + p.WorkingDirectory = Directory.GetParent(dir).FullName; + p.Arguments = "-cf "+jarFile+" "+ Path.GetFileName(dir); + p.UseShellExecute = false; +// p.RedirectStandardOutput = true; +// p.RedirectStandardError = true; + System.Diagnostics.Process.Start(p); + System.Diagnostics.Process pr = new System.Diagnostics.Process(); + pr.StartInfo = p; + pr.Start(); + pr.WaitForExit(); + + String[] arg = new string[]{file, user+"@"+host+":"+path+Path.GetFileName(file)}; + //Tamir.SharpSsh.Scp.To(file, host, path+Path.GetFileName(file), user, pass); + } + + public static void print(string name, byte[] data) + { + Console.WriteLine(); + Console.Write(name+": "); + Console.WriteLine( hex(data) ); + Console.WriteLine(); + } + + public static string hex(byte[] arr) + { + string hex = "0x"; + for(int i=0;i + /// Class for handling SCP file transfers over SSH connection. + /// + public class Scp : SshTransferProtocolBase + { + private bool m_recursive = false; + private bool m_verbos = false; + private bool m_cancelled = false; + + public Scp(string host, string user, string password) + : base(host, user, password) + { + } + + public Scp(string host, string user) + : base(host, user) + { + } + + protected override string ChannelType + { + get { return "exec"; } + } + + /// + ///This function is empty, so no channel is connected + ///on session connect + /// + protected override void ConnectChannel() + { + } + + /// + /// Gets or sets a value indicating the default recursive transfer behaviour + /// + public bool Recursive + { + get{return m_recursive;} + set{m_recursive=value;} + } + + /// + /// Gets or sets a value indicating whether trace information should be printed + /// + public bool Verbos + { + get{return m_verbos;} + set{m_verbos=value;} + } + + public override void Cancel() + { + m_cancelled = true; + } + + /// + /// Creates a directory on the remot server + /// + /// The new directory + public override void Mkdir(string dir) + { + SCP_CheckConnectivity(); + + Channel channel=null; + Stream server = null; + m_cancelled=false; + + SCP_ConnectTo(out channel, out server, dir, true); + SCP_EnterIntoDir(server, dir); + channel.disconnect(); + } + + public override void Put(string fromFilePath, string toFilePath) + { + this.To(fromFilePath, toFilePath); + } + + public override void Get(string fromFilePath, string toFilePath) + { + this.From(fromFilePath, toFilePath); + } + + + + /// + /// Copies a file from local machine to a remote SSH machine. + /// + /// The local file path. + /// The path of the remote file. + public void To(string localPath, string remotePath) + { + this.To(localPath, remotePath, Recursive); + } + + + /// + /// Copies a file from local machine to a remote SSH machine. + /// + /// The local file path. + /// The path of the remote file. + public void To(string localPath, string remotePath, bool _recursive) + { + SCP_CheckConnectivity(); + + Channel channel=null; + Stream server = null; + m_cancelled=false; + + try + { + //if we are sending a single file + if(File.Exists(localPath)) + { + SCP_ConnectTo(out channel, out server, remotePath, _recursive); + SCP_SendFile(server, localPath, remotePath); + channel.disconnect(); + } + //else, if we are sending a local directory + else if(Directory.Exists(localPath)) + { + if(!_recursive) + { + throw new SshTransferException(Path.GetFileName("'"+localPath)+"' is a directory, you should use recursive transfer."); + } + SCP_ConnectTo(out channel, out server, remotePath, true); + ToRecursive(server, localPath, remotePath); + channel.disconnect(); + } + else + { + throw new SshTransferException("File not found: "+localPath); + } + } + catch(Exception e) + { + if(Verbos) + Console.WriteLine("Error: "+e.Message); + //SendEndMessage(remoteFile, localFile, filesize,filesize, "Transfer ended with an error."); + try{channel.disconnect();} + catch{} + throw e; + } + } + + /// + /// Copies files and directories from local machine to a remote SSH machine using SCP. + /// + /// I/O Stream for the remote server + /// Source to copy + /// Destination path + private void ToRecursive(Stream server, string src, string dst) + { + if(Directory.Exists(src)) + { + SCP_EnterIntoDir(server, Path.GetFileName(dst)); + foreach(string file in Directory.GetFiles(src)) + { + SCP_SendFile(server, file, Path.GetFileName( file)); + } + if(m_cancelled) + { + return; + } + foreach(string dir in Directory.GetDirectories(src)) + { + ToRecursive(server, dir, Path.GetFileName(dir)); + } + SCP_EnterIntoParent(server); + } + else if(File.Exists(src)) + { + SCP_SendFile(server, src, Path.GetFileName(src)); + } + else + { + throw new SshTransferException("File not found: "+src); + } + } + + /// + /// Copies a file from a remote SSH machine to the local machine using SCP. + /// + /// The remmote file name + /// The local destination path + public void From(string remoteFile, string localPath) + { + this.From(remoteFile, localPath, Recursive); + } + + /// + /// Copies a file from a remote SSH machine to the local machine using SCP. + /// + /// The remmote file name + /// The local destination path + /// Value indicating whether a recursive transfer should take place + public void From(string remoteFile, string localPath, bool _recursive) + { + SCP_CheckConnectivity(); + + Channel channel=null; + Stream server = null; + m_cancelled=false; + int filesize=0; + String filename=null; + string cmd = null; + + try + { + String dir=null; + if(Directory.Exists(localPath)) + { + dir= Path.GetFullPath( localPath ); + } + + SCP_ConnectFrom(out channel, out server, remoteFile, _recursive); + + byte[] buf=new byte[1024]; + + // send '\0' + SCP_SendAck(server); + int c=SCP_CheckAck(server); + + //parse scp commands + while((c=='D')||(c=='C')||(c=='E')) + { + if(m_cancelled) + break; + + cmd = ""+(char)c; + if(c=='E') + { + c=SCP_CheckAck(server); + dir = Path.GetDirectoryName(dir); + if(Verbos) Console.WriteLine("E"); + //send '\0' + SCP_SendAck(server); + c=(char)SCP_CheckAck(server); + continue; + } + + // read '0644 ' or '0755 ' + server.Read(buf, 0, 5); + for(int i=0;i<5;i++) + cmd+=(char)buf[i]; + + //reading file size + filesize=0; + while(true) + { + server.Read(buf, 0, 1); + if(buf[0]==' ')break; + filesize=filesize*10+(buf[0]-'0'); + } + + //reading file name + for(int i=0;;i++) + { + server.Read(buf, i, 1); + if(buf[i]==(byte)0x0a) + { + filename=Util.getString(buf, 0, i); + break; + } + } + cmd += " "+filesize+" "+filename; + // send '\0' + SCP_SendAck(server); + + //Receive file + if(c=='C') + { + if(Verbos) Console.WriteLine("Sending file modes: "+cmd); + SCP_ReceiveFile(server, remoteFile, + dir==null?localPath:dir+"/"+filename, + filesize); + + if(m_cancelled) + break; + + // send '\0' + SCP_SendAck(server); + } + //Enter directory + else if(c=='D') + { + if(dir==null) + { + if(File.Exists(localPath)) throw new SshTransferException("'"+localPath+"' is not a directory"); + dir = localPath; + Directory.CreateDirectory(dir); + } + if(Verbos) Console.WriteLine("Entering directory: "+cmd); + dir += "/"+filename; + Directory.CreateDirectory(dir); + } + + c=SCP_CheckAck(server); + } + channel.disconnect(); + } + catch(Exception e) + { + if(Verbos) + Console.WriteLine("Error: "+e.Message); + try + { + channel.disconnect(); + } + catch{} + throw e; + } + } + + #region SCP private functions + + /// + /// Checks is a channel is already connected by this instance + /// + protected void SCP_CheckConnectivity() + { + if(!Connected) + throw new Exception("Channel is down."); + } + + /// + /// Connect a channel to the remote server using the 'SCP TO' command ('scp -t') + /// + /// Will contain the new connected channel + /// Will contaun the new connected server I/O stream + /// The remote path on the server + /// Idicate a recursive scp transfer + protected void SCP_ConnectTo(out Channel channel, out Stream server, string rfile, bool recursive) + { + string scpCommand = "scp -p -t "; + if(recursive) scpCommand += "-r "; + scpCommand += "\""+rfile+"\""; + + channel = (ChannelExec)m_session.openChannel(ChannelType); + ((ChannelExec)channel).setCommand(scpCommand); + + server = + new Tamir.Streams.CombinedStream + (channel.getInputStream(), channel.getOutputStream()); + channel.connect(); + + SCP_CheckAck(server); + } + + /// + /// Connect a channel to the remote server using the 'SCP From' command ('scp -f') + /// + /// Will contain the new connected channel + /// Will contaun the new connected server I/O stream + /// The remote path on the server + /// Idicate a recursive scp transfer + protected void SCP_ConnectFrom(out Channel channel, out Stream server, string rfile, bool recursive) + { + string scpCommand = "scp -f "; + if(recursive) scpCommand += "-r "; + scpCommand += "\""+rfile+"\""; + + channel = (ChannelExec)m_session.openChannel(ChannelType); + ((ChannelExec)channel).setCommand(scpCommand); + + server = + new Tamir.Streams.CombinedStream + (channel.getInputStream(), channel.getOutputStream()); + channel.connect(); + + //SCP_CheckAck(server); + } + + /// + /// Transfer a file to the remote server + /// + /// A connected server I/O stream + /// The source file to copy + /// The remote destination path + protected void SCP_SendFile(Stream server, string src, string dst) + { + int filesize = 0; + int copied = 0; + + filesize=(int)(new FileInfo(src)).Length; + + byte[] tmp=new byte[1]; + + // send "C0644 filesize filename", where filename should not include '/' + + string command="C0644 "+filesize+" "+Path.GetFileName(dst)+"\n"; + if(Verbos) Console.WriteLine("Sending file modes: "+command); + SendStartMessage(src, dst, filesize, "Starting transfer."); + byte[] buff = Util.getBytes(command); + server.Write(buff, 0, buff.Length); server.Flush(); + + if(SCP_CheckAck(server)!=0) + { + throw new SshTransferException("Error openning communication channel."); + } + + // send a content of lfile + SendProgressMessage(src, dst, copied, filesize, "Transferring..."); + FileStream fis=File.OpenRead(src); + byte[] buf=new byte[1024*10*2]; + + while(!m_cancelled) + { + int len=fis.Read(buf, 0, buf.Length); + if(len<=0) break; + server.Write(buf, 0, len); server.Flush(); + copied += len; + SendProgressMessage(src, dst, copied, filesize, "Transferring..."); + } + fis.Close(); + + if(m_cancelled) + return; + + // send '\0' + buf[0]=0; server.Write(buf, 0, 1); server.Flush(); + + SendProgressMessage(src, dst, copied, filesize, "Verifying transfer..."); + if(SCP_CheckAck(server)!=0) + { + SendEndMessage(src, dst,copied,filesize, "Transfer ended with an error."); + throw new SshTransferException("Unknow error during file transfer."); + } + SendEndMessage(src, dst, copied, filesize, "Transfer completed successfuly ("+copied+" bytes)."); + } + + /// + /// Transfer a file from the remote server + /// + /// A connected server I/O stream + /// The remote file to copy + /// The local destination path + protected void SCP_ReceiveFile(Stream server, string rfile, string lfile, int size) + { + int copied = 0; + SendStartMessage(rfile, lfile, size, "Connected, starting transfer."); + // read a content of lfile + FileStream fos=File.OpenWrite(lfile); + int foo; + int filesize=size; + byte[] buf = new byte[1024]; + while(!m_cancelled) + { + if(buf.Length + /// Instructs the remote server to enter into a directory + /// + /// A connected server I/O stream + /// The directory name/param> + protected void SCP_EnterIntoDir(Stream server, string dir) + { + try + { + byte[] tmp=new byte[1]; + + // send "C0644 filesize filename", where filename should not include '/' + + string command="D0755 0 "+Path.GetFileName(dir)+"\n"; + if(Verbos) Console.WriteLine("Enter directory: "+command); + + byte[] buff = Util.getBytes(command); + server.Write(buff, 0, buff.Length); server.Flush(); + + if(SCP_CheckAck(server)!=0) + { + throw new SshTransferException("Error openning communication channel."); + } + } + catch{} + } + + /// + /// Instructs the remote server to go up one level + /// + /// A connected server I/O stream + protected void SCP_EnterIntoParent(Stream server) + { + try + { + byte[] tmp=new byte[1]; + + // send "C0644 filesize filename", where filename should not include '/' + + string command="E\n"; + if(Verbos) Console.WriteLine(command); + + byte[] buff = Util.getBytes(command); + server.Write(buff, 0, buff.Length); server.Flush(); + + if(SCP_CheckAck(server)!=0) + { + throw new SshTransferException("Error openning communication channel."); + } + } + catch{} + } + + /// + /// Gets server acknowledgment + /// + /// A connected server I/O stream + private int SCP_CheckAck(Stream ins) + { + int b=ins.ReadByte(); + // b may be 0 for success, + // 1 for error, + // 2 for fatal error, + // -1 + if(b==0) return b; + if(b==-1) return b; + + if(b==1 || b==2) + { + StringBuilder sb=new StringBuilder(); + int c; + do + { + c=ins.ReadByte(); + sb.Append((char)c); + } + while(c!='\n'); + if(b==1) + { // error + //Console.WriteLine(sb.ToString()); + throw new SshTransferException(sb.ToString()); + } + if(b==2) + { // fatal error + //Console.WriteLine(sb.ToString()); + throw new SshTransferException(sb.ToString()); + } + } + return b; + } + + /// + /// Sends acknowledgment to remote server + /// + /// A connected server I/O stream + private void SCP_SendAck(Stream server) + { + server.WriteByte(0); + server.Flush(); + } + + #endregion SCP private functions + } +} diff --git a/SharpSSH/Scp.old.cs b/SharpSSH/Scp.old.cs new file mode 100644 index 00000000..06290ffa --- /dev/null +++ b/SharpSSH/Scp.old.cs @@ -0,0 +1,354 @@ +//using System; +//using Tamir.SharpSsh.jsch; +//using System.IO; +//using System.Windows.Forms; +//using System.Text; +//using System.Collections; +// +///* +// * Scp.cs +// * +// * THIS SOURCE CODE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY +// * KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// * IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR +// * PURPOSE. +// * +// * Copyright (C) 2005 Tamir Gal, tamirgal@myrealbox.com. +// */ +// +//namespace Tamir.SharpSsh +//{ +// /// +// /// Class for handling SCP file transfers over SSH connection. +// /// +// public class Scp +// { +// /// +// /// Triggered when this SCP object starts connecting to the remote server. +// /// +// public event FileTansferEvent OnConnecting; +// /// +// /// Triggered when this SCP object starts the file transfer process. +// /// +// public event FileTansferEvent OnStart; +// /// +// /// Triggered when this SCP object ends the file transfer process. +// /// +// public event FileTansferEvent OnEnd; +// /// +// /// Triggered on every interval with the transfer progress iformation. +// /// +// public event FileTansferEvent OnProgress; +// +// /// +// /// The default value of the progress update interval. +// /// +// private int m_interval = 250; +// +// /// +// /// Copies a file from local machine to a remote SSH machine. +// /// +// /// The local file path. +// /// The remote machine's hostname or IP address +// /// The path of the remote file. +// /// The username for the connection. +// /// The password for the connection. +// public void To(string localFile, string remoteHost,string remoteFile, string user, string pass) +// { +// Channel channel=null; +// int filesize=0; +// int copied=0; +// try +// { +// double progress=0; +// SendConnectingMessage("Connecting to "+remoteHost+"..."); +// +// JSch jsch=new JSch(); +// Session session=jsch.getSession(user, remoteHost, 22); +// session.setPassword( pass ); +// +// Hashtable config=new Hashtable(); +// config.Add("StrictHostKeyChecking", "no"); +// session.setConfig(config); +// +// session.connect(); +// +// // exec 'scp -t rfile' remotely +// String command="scp -p -t \""+remoteFile+"\""; +// channel=session.openChannel("exec"); +// ((ChannelExec)channel).setCommand(command); +// +// // get I/O streams for remote scp +// Stream outs=channel.getOutputStream(); +// Stream ins=channel.getInputStream(); +// +// channel.connect(); +// +// SendStartMessage("Connected, starting transfer."); +// +// byte[] tmp=new byte[1]; +// +// if(checkAck(ins)!=0) +// { +// throw new Exception("Error openning communication channel."); +// } +// +// // send "C0644 filesize filename", where filename should not include '/' +// +// filesize=(int)(new FileInfo(localFile)).Length; +// command="C0644 "+filesize+" "; +// if(localFile.LastIndexOf('/')>0) +// { +// command+=localFile.Substring(localFile.LastIndexOf('/')+1); +// } +// else +// { +// command+=localFile; +// } +// command+="\n"; +// byte[] buff = Util.getBytes(command); +// outs.Write(buff, 0, buff.Length); outs.Flush(); +// +// if(checkAck(ins)!=0) +// { +// throw new Exception("Error openning communication channel."); +// } +// +// // send a content of lfile +// SendProgressMessage(0, filesize, "Transferring..."); +// FileStream fis=File.OpenRead(localFile); +// byte[] buf=new byte[1024]; +// copied = 0; +// while(true) +// { +// int len=fis.Read(buf, 0, buf.Length); +// if(len<=0) break; +// outs.Write(buf, 0, len); outs.Flush(); +// copied += len; +// progress = (copied*100.0/filesize); +// SendProgressMessage(copied, filesize, "Transferring..."); +// } +// fis.Close(); +// +// // send '\0' +// buf[0]=0; outs.Write(buf, 0, 1); outs.Flush(); +// +// SendProgressMessage(copied, filesize, "Verifying transfer..."); +// if(checkAck(ins)!=0) +// { +// throw new Exception("Unknow error during file transfer."); +// } +// SendEndMessage(copied, filesize, "Transfer completed successfuly ("+copied+" bytes)."); +// try{channel.close();} +// catch{} +// } +// catch(Exception e) +// { +// SendEndMessage(copied,filesize, "Transfer ended with an error."); +// try{channel.close();} +// catch{} +// throw e; +// } +// } +// +// /// +// /// Copies a file from a remote SSH machine to the local machine. +// /// +// /// The remote machine's hosname or IP address. +// /// The remote file path. +// /// The username or the connection. +// /// The password for the connection. +// /// The local file path. +// public void From(string remoteHost,string remoteFile, string user, string pass, string localFile) +// { +// Channel channel=null; +// int filesize=0; +// int copied=0; +// try +// { +// String prefix=null; +// if(Directory.Exists(localFile)) +// { +// prefix=localFile+Path.DirectorySeparatorChar; +// } +// +// double progress=0; +// SendConnectingMessage("Connecting to "+remoteHost+"..."); +// +// JSch jsch=new JSch(); +// Session session=jsch.getSession(user, remoteHost, 22); +// session.setPassword( pass ); +// +// Hashtable config=new Hashtable(); +// config.Add("StrictHostKeyChecking", "no"); +// session.setConfig(config); +// +// session.connect(); +// +// // exec 'scp -f rfile' remotely +// String command="scp -f \""+remoteFile + "\""; +// channel=session.openChannel("exec"); +// ((ChannelExec)channel).setCommand(command); +// +// // get I/O streams for remote scp +// Stream outs=channel.getOutputStream(); +// Stream ins=channel.getInputStream(); +// +// channel.connect(); +// +// SendStartMessage("Connected, starting transfer."); +// +// byte[] buf=new byte[1024]; +// +// // send '\0' +// buf[0]=0; outs.Write(buf, 0, 1); outs.Flush(); +// int c=checkAck(ins); +// if(c!='C') +// { +// throw new Exception("Error openning communication channel."); +// } +// +// // read '0644 ' +// ins.Read(buf, 0, 5); +// +// filesize=0; +// while(true) +// { +// ins.Read(buf, 0, 1); +// if(buf[0]==' ')break; +// filesize=filesize*10+(buf[0]-'0'); +// } +// +// String file=null; +// for(int i=0;;i++) +// { +// ins.Read(buf, i, 1); +// if(buf[i]==(byte)0x0a) +// { +// file=Util.getString(buf, 0, i); +// break; +// } +// } +// +// // send '\0' +// buf[0]=0; outs.Write(buf, 0, 1); outs.Flush(); +// +// // read a content of lfile +// FileStream fos=File.OpenWrite(prefix==null ? +// localFile : +// prefix+file); +// int foo; +// int size=filesize; +// copied=0; +// while(true) +// { +// if(buf.LengthProgressUpdateInterval) +// { +// OnProgress(transferredBytes,totalBytes, msg); +// lastUpdate=DateTime.Now; +// } +// } +// } +// +// /// +// /// Gets or sets the progress update interval in milliseconds +// /// +// public int ProgressUpdateInterval +// { +// get{return m_interval;} +// set{m_interval=value;} +// } +// } +// +// public delegate void FileTansferEvent(int transferredBytes, int totalBytes, string message); +//} diff --git a/SharpSSH/SecureShell.cs b/SharpSSH/SecureShell.cs new file mode 100644 index 00000000..e9b260bf --- /dev/null +++ b/SharpSSH/SecureShell.cs @@ -0,0 +1,17 @@ +using System; + +namespace Tamir.SharpSsh +{ + /// + /// Summary description for SecureShell. + /// + public class SecureShell + { + public SecureShell() + { + // + // TODO: Add constructor logic here + // + } + } +} diff --git a/SharpSSH/Sftp.cs b/SharpSSH/Sftp.cs new file mode 100644 index 00000000..62fb31cc --- /dev/null +++ b/SharpSSH/Sftp.cs @@ -0,0 +1,219 @@ +using System; +using Tamir.SharpSsh.jsch; +using System.Collections; + +/* + * Sftp.cs + * + * Copyright (c) 2006 Tamir Gal, http://www.tamirgal.com, All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the distribution. + * + * 3. The names of the authors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR + * *OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + **/ + +namespace Tamir.SharpSsh +{ + public class Sftp : SshTransferProtocolBase + { + private MyProgressMonitor m_monitor; + private bool cancelled = false; + + public Sftp(string sftpHost, string user, string password) + : base(sftpHost, user, password) + { + Init(); + } + + public Sftp(string sftpHost, string user) + : base(sftpHost, user) + { + Init(); + } + + private void Init() + { + m_monitor = new MyProgressMonitor(this); + } + + protected override string ChannelType + { + get { return "sftp"; } + } + + private ChannelSftp SftpChannel + { + get { return (ChannelSftp)m_channel; } + } + + public override void Cancel() + { + cancelled = true; + } + + //Get + + public void Get(string fromFilePath) + { + Get(fromFilePath, "."); + } + + public void Get(string[] fromFilePaths) + { + for (int i = 0; i < fromFilePaths.Length; i++) + { + Get(fromFilePaths[i]); + } + } + + public void Get(string[] fromFilePaths, string toDirPath) + { + for (int i = 0; i < fromFilePaths.Length; i++) + { + Get(fromFilePaths[i], toDirPath); + } + } + + public override void Get(string fromFilePath, string toFilePath) + { + cancelled=false; + SftpChannel.get(fromFilePath, toFilePath, m_monitor, ChannelSftp.OVERWRITE); + } + + //Put + + public void Put(string fromFilePath) + { + Put(fromFilePath, "."); + } + + public void Put(string[] fromFilePaths) + { + for (int i = 0; i < fromFilePaths.Length; i++) + { + Put(fromFilePaths[i]); + } + } + + public void Put(string[] fromFilePaths, string toDirPath) + { + for (int i = 0; i < fromFilePaths.Length; i++) + { + Put(fromFilePaths[i], toDirPath); + } + } + + public override void Put(string fromFilePath, string toFilePath) + { + cancelled=false; + SftpChannel.put(fromFilePath, toFilePath, m_monitor, ChannelSftp.OVERWRITE); + } + + //MkDir + + public override void Mkdir(string directory) + { + SftpChannel.mkdir(directory); + } + + //Ls + + public ArrayList GetFileList(string path) + { + ArrayList list = new ArrayList(); + foreach(Tamir.SharpSsh.jsch.ChannelSftp.LsEntry entry in SftpChannel.ls(path)) + { + list.Add(entry.getFilename().ToString()); + } + return list; + } + + #region ProgressMonitor Implementation + + private class MyProgressMonitor : SftpProgressMonitor + { + private long transferred = 0; + private long total = 0; + private int elapsed = -1; + private Sftp m_sftp; + private string src; + private string dest; + + System.Timers.Timer timer; + + public MyProgressMonitor(Sftp sftp) + { + m_sftp = sftp; + } + + public override void init(int op, String src, String dest, long max) + { + this.src=src; + this.dest=dest; + this.elapsed = 0; + this.total = max; + timer = new System.Timers.Timer(1000); + timer.Start(); + timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed); + + string note; + if (op.Equals(GET)) + { + note = "Downloading " + System.IO.Path.GetFileName( src ) + "..."; + } + else + { + note = "Uploading " + System.IO.Path.GetFileName( src ) + "..."; + } + m_sftp.SendStartMessage(src, dest, (int)total, note); + } + public override bool count(long c) + { + this.transferred += c; + string note = ("Transfering... [Elapsed time: " + elapsed + "]"); + m_sftp.SendProgressMessage(src, dest, (int)transferred, (int)total, note); + return !m_sftp.cancelled; + } + public override void end() + { + timer.Stop(); + timer.Dispose(); + string note = ("Done in " + elapsed + " seconds!"); + m_sftp.SendEndMessage(src, dest, (int)transferred, (int)total, note); + transferred = 0; + total = 0; + elapsed = -1; + src=null; + dest=null; + } + + private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) + { + this.elapsed++; + } + } + + #endregion ProgressMonitor Implementation + } +} diff --git a/SharpSSH/SharpSSH.csproj b/SharpSSH/SharpSSH.csproj new file mode 100644 index 00000000..84636d8c --- /dev/null +++ b/SharpSSH/SharpSSH.csproj @@ -0,0 +1 @@ + Debug AnyCPU 8.0.50727 2.0 {BB50B7E2-4622-4D8B-B7FF-5E5D8F02D91F} Library Tamir.SharpSsh Tamir.SharpSSH true full false ..\bin\Debug\ prompt 4 false none false ..\bin\Release\ prompt 4 false InputForm.cs False lib\DiffieHellman.dll False lib\Org.Mentalis.Security.dll \ No newline at end of file diff --git a/SharpSSH/SharpSSH.csproj.user b/SharpSSH/SharpSSH.csproj.user new file mode 100644 index 00000000..6219a221 --- /dev/null +++ b/SharpSSH/SharpSSH.csproj.user @@ -0,0 +1,48 @@ + + + + + + + + + + + + diff --git a/SharpSSH/SharpSSH.mdp b/SharpSSH/SharpSSH.mdp new file mode 100644 index 00000000..0cb8b6ed --- /dev/null +++ b/SharpSSH/SharpSSH.mdp @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SharpSSH/SshBase.cs b/SharpSSH/SshBase.cs new file mode 100644 index 00000000..02c2d9aa --- /dev/null +++ b/SharpSSH/SshBase.cs @@ -0,0 +1,335 @@ +using System; +using System.Collections; +using Tamir.SharpSsh.jsch; + +/* + * SshBase.cs + * + * Copyright (c) 2006 Tamir Gal, http://www.tamirgal.com, All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the distribution. + * + * 3. The names of the authors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR + * *OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + **/ +namespace Tamir.SharpSsh +{ + /// + /// A wrapper class for JSch's SSH channel + /// + public abstract class SshBase + { + protected string m_host; + protected string m_user; + protected string m_pass; + protected JSch m_jsch; + protected Session m_session; + protected Channel m_channel; + + /// + /// Default TCP port of SSH protocol + /// + private static int SSH_TCP_PORT = 22; + + /// + /// Constructs a new SSH instance + /// + /// The remote SSH host + /// The login username + /// The login password + public SshBase(string sftpHost, string user, string password) + { + this.m_host = sftpHost; + this.m_user = user; + this.Password = password; + m_jsch = new JSch(); + } + + /// + /// Constructs a new SSH instance + /// + /// The remote SSH host + /// The login username + public SshBase(string sftpHost, string user) + : this(sftpHost, user, null) + { + } + + /// + /// Adds identity file for publickey user authentication + /// + /// The path to the private key file + public virtual void AddIdentityFile(string privateKeyFile) + { + m_jsch.addIdentity(privateKeyFile); + } + + /// + /// Adds identity file for publickey user authentication + /// + /// The path to the private key file + /// A passphrase for decrypting the private key file + public virtual void AddIdentityFile(string privateKeyFile, string passphrase) + { + m_jsch.addIdentity(privateKeyFile, passphrase); + } + + protected abstract string ChannelType{get;} + + /// + /// Connect to remote SSH server + /// + public virtual void Connect() + { + this.Connect(SSH_TCP_PORT); + } + + /// + /// Connect to remote SSH server + /// + /// The destination TCP port for this connection + public virtual void Connect(int tcpPort) + { + this.ConnectSession(tcpPort); + this.ConnectChannel(); + } + + protected virtual void ConnectSession(int tcpPort) + { + m_session = m_jsch.getSession(m_user, m_host, tcpPort); + if (Password != null) + m_session.setUserInfo(new KeyboardInteractiveUserInfo(Password)); + Hashtable config = new Hashtable(); + config.Add("StrictHostKeyChecking", "no"); + m_session.setConfig(config); + m_session.connect(); + } + + protected virtual void ConnectChannel() + { + m_channel = m_session.openChannel(ChannelType); + this.OnChannelReceived(); + m_channel.connect(); + this.OnConnected(); + } + + protected virtual void OnConnected() + { + } + + protected virtual void OnChannelReceived() + { + } + + /// + /// Closes the SSH subsystem + /// + public virtual void Close() + { + if (m_channel != null) + { + m_channel.disconnect(); + m_channel = null; + } + if (m_session != null) + { + m_session.disconnect(); + m_session = null; + } + } + + /// + /// Return true if the SSH subsystem is connected + /// + public virtual bool Connected + { + get + { + if (m_session != null) + return m_session.isConnected(); + return false; + } + } + + /// + /// Gets the Cipher algorithm name used in this SSH connection. + /// + public string Cipher + { + get + { + CheckConnected(); + return m_session.getCipher(); + } + } + + /// + /// Gets the MAC algorithm name used in this SSH connection. + /// + public string Mac + { + get + { + CheckConnected(); + return m_session.getMac(); + } + } + + /// + /// Gets the server SSH version string. + /// + public string ServerVersion + { + get + { + CheckConnected(); + return m_session.getServerVersion(); + } + } + + /// + /// Gets the client SSH version string. + /// + public string ClientVersion + { + get + { + CheckConnected(); + return m_session.getClientVersion(); + } + } + + public string Host + { + get + { + CheckConnected(); + return m_session.getHost(); + } + } + + public HostKey HostKey + { + get + { + CheckConnected(); + return m_session.getHostKey(); + } + } + + public int Port + { + get + { + CheckConnected(); + return m_session.getPort(); + } + } + + /// + /// The password string of the SSH subsystem + /// + public string Password + { + get { return m_pass; } + set { m_pass = value; } + } + public string Username + { + get { return m_user; } + } + + public static Version Version + { + get + { + System.Reflection.Assembly asm + = System.Reflection.Assembly.GetAssembly(typeof(Tamir.SharpSsh.SshBase)); + return asm.GetName().Version; + } + } + + private void CheckConnected() + { + if(!Connected) + { + throw new Exception("SSH session is not connected."); + } + } + + /// + /// For password and KI auth modes + /// + protected class KeyboardInteractiveUserInfo : UserInfo, UIKeyboardInteractive + { + string _password; + + public KeyboardInteractiveUserInfo(string password) + { + _password = password; + } + + #region UIKeyboardInteractive Members + + public string[] promptKeyboardInteractive(string destination, string name, string instruction, string[] prompt, bool[] echo) + { + return new string[] { _password }; + } + + #endregion + + #region UserInfo Members + + public bool promptYesNo(string message) + { + return true; + } + + public bool promptPassword(string message) + { + return true; + } + + public string getPassword() + { + return _password; + } + + public bool promptPassphrase(string message) + { + return true; + } + + public string getPassphrase() + { + return null; + } + + public void showMessage(string message) + { + } + + #endregion + } + } +} diff --git a/SharpSSH/SshExe.cs b/SharpSSH/SshExe.cs new file mode 100644 index 00000000..7b96411a --- /dev/null +++ b/SharpSSH/SshExe.cs @@ -0,0 +1,121 @@ +using System; +using Tamir.SharpSsh.jsch; +using System.Text; + +/* + * SshExe.cs + * + * Copyright (c) 2006 Tamir Gal, http://www.tamirgal.com, All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the distribution. + * + * 3. The names of the authors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR + * *OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + **/ +namespace Tamir.SharpSsh +{ + /// + /// Summary description for SshExe. + /// + public class SshExec : SshBase + { + public SshExec(string host, string user, string password) + : base(host, user, password) + { + } + + public SshExec(string host, string user) + : base(host, user) + { + } + + protected override string ChannelType + { + get { return "exec"; } + } + + /// + ///This function is empty, so no channel is connected + ///on session connect + /// + protected override void ConnectChannel() + { + } + + protected ChannelExec GetChannelExec(string command) + { + ChannelExec exeChannel = (ChannelExec)m_session.openChannel("exec"); + exeChannel.setCommand(command); + return exeChannel; + } + + public string RunCommand(string command) + { + m_channel = GetChannelExec(command); + System.IO.Stream s = m_channel.getInputStream(); + m_channel.connect(); + byte[] buff = new byte[1024]; + StringBuilder res = new StringBuilder(); + int c = 0; + while(true) + { + c = s.Read(buff, 0, buff.Length); + if(c==-1) break; + res.Append( Encoding.ASCII.GetString(buff, 0, c) ); + //Console.WriteLine(res); + } + m_channel.disconnect(); + return res.ToString(); + } + + public int RunCommand(string command, ref string StdOut, ref string StdErr) + { + StdOut = ""; + StdErr = ""; + m_channel = GetChannelExec(command); + System.IO.Stream stdout = m_channel.getInputStream(); + System.IO.Stream stderr = ((ChannelExec)m_channel).getErrStream(); + m_channel.connect(); + byte[] buff = new byte[1024]; + StringBuilder sbStdOut = new StringBuilder(); + StringBuilder sbStdErr = new StringBuilder(); + int o=0; int e=0; + while(true) + { + if(o!=-1) o = stdout.Read(buff, 0, buff.Length); + if(o!=-1) StdOut += sbStdOut.Append(Encoding.ASCII.GetString(buff, 0, o)); + if(e!=-1) e = stderr.Read(buff, 0, buff.Length); + if(e!=-1) StdErr += sbStdErr.Append(Encoding.ASCII.GetString(buff, 0, e)); + if((o==-1)&&(e==-1)) break; + } + m_channel.disconnect(); + + return m_channel.getExitStatus(); + } + + public ChannelExec ChannelExec + { + get{return (ChannelExec)this.m_channel;} + } + } +} diff --git a/SharpSSH/SshHelper.cs b/SharpSSH/SshHelper.cs new file mode 100644 index 00000000..f18a4bd5 --- /dev/null +++ b/SharpSSH/SshHelper.cs @@ -0,0 +1,220 @@ +using System.IO; +using System.Collections; +using Tamir.SharpSsh.jsch; +using Tamir.Streams; +using System.Text; + +namespace Tamir +{ + public class SshHelper + { + + private StreamReader reader; + private PipedOutputStream writer_po; + private Session session; + private ChannelShell channel; + private string host; + + public SshHelper(string host, string username, string password) + { + this.host = host; + JSch jsch=new JSch(); + Session session=jsch.getSession(username, host, 22); + session.setPassword( password ); + + Hashtable config=new Hashtable(); + config.Add("StrictHostKeyChecking", "no"); + session.setConfig(config); + + session.connect(); + + channel=(ChannelShell)session.openChannel("shell"); + + writer_po = new PipedOutputStream(); + PipedInputStream writer_pi = new PipedInputStream( writer_po ); + + PipedInputStream reader_pi = new PipedInputStream(); + PipedOutputStream reader_po = new PipedOutputStream( reader_pi ); + reader = new StreamReader (reader_pi,Encoding.UTF8); + + + channel.setInputStream( writer_pi ); + channel.setOutputStream( reader_po ); + + channel.connect(); + channel.setPtySize(132, 132, 1024, 768); + + } + + public void Write(string data) + { + data += "\n"; + writer_po.write( Util.getBytes(data )); + } + + public string WriteAndRead(string data) + { + Write( data ); + return ReadResponse(); + } + + public void SendCtrlC() + { + byte ASCII_CTRL_C = 3; + byte[] ctrlc = { ASCII_CTRL_C }; + writer_po.write( ctrlc ); + } + + public void SendCtrlZ() + { + byte ASCII_CTRL_Z = 26; + byte[] ctrlz = { ASCII_CTRL_Z }; + writer_po.write( ctrlz ); + } + + public string ReadResponse() + { + // return ReadLine(); + return ReadTest(); + } + + public string ReadBuffer(int size) + { + string res = ""; + int buff; + try + { + buff = reader.Read(); + while( ((char)buff != '#') && (size != 0) ) + { + res += (char)buff; + size--; + if (res.EndsWith("More--")) + writer_po.write(Util.getBytes( " ") ); + buff = reader.Read(); + } + if(buff != '\n') + res += (char)buff; + } + catch//(Exception exc) + { + } + res = RemoveJunk( res ); + return res; + } + + public void Close() + { + channel.disconnect(); + writer_po.close(); + reader.Close(); + } + +// private string ReadLine() +// { +// string res = ""; +// int buff; +// try +// { +// buff = reader.ReadByte(); +// while((char)buff != '#') +// { +// res += (char)buff; +// if (res.EndsWith("More--")) +// writer_po.write( Util.getBytes(" ") ); +// buff = reader.ReadByte(); +// } +// if(buff != '\n') +// res += (char)buff; +// } +// catch//Exception exc) +// { +// } +// res = RemoveJunk( res ); +// return res; +// } + + private string ReadTest() + { + StringBuilder res = new StringBuilder(); + char[] buff = new char[1024]; + int count = 0; + + try + { + count = reader.Read(buff, 0, buff.Length); + //ArrayList lstBuff = new ArrayList(java.util.Arrays.asList(buff)); + while(!Util.ArrayContains(buff, '#', count)) + { + // while(buff[count-2]!='#' ){ + res.Append( buff, 0, count ); + if ( (res.ToString().EndsWith("More--"))||(res.ToString().EndsWith("More--"))) + writer_po.write( Util.getBytes( " ") ); + count = reader.Read(buff, 0, buff.Length); + count++; + count--; + //lstBuff = new ArrayList(java.util.Arrays.asList(buff)); + } + if(buff[count-1] != '\n') + res.Append( buff, 0, count ); + else + res.Append( buff, 0, count-1 ); + } + catch//(Exception exc) + { + } + return RemoveJunk( res ).ToString(); + } + + private static string RemoveJunk(string str) + { + string[] junk = new string[]{"", "", "", "", "", " \b", "--More--", "\r", "\n "}; + + for( int i=0; i + /// Summary description for SshShell. + /// + public class SshShell : SshBase + { + private Stream m_sshIO = null; + private Regex m_expectPattern; + private bool m_removeTerminalChars = false; + private bool m_redirectToConsole = false; + private static string escapeCharsPattern = "\\[[0-9;?]*[^0-9;]"; + + public SshShell(string host, string user, string password) + : base(host, user, password) + { + Init(); + } + + public SshShell(string host, string user) + : base(host, user) + { + Init(); + } + + protected void Init() + { + ExpectPattern = ""; + m_removeTerminalChars = false; + } + + protected override void OnChannelReceived() + { + base.OnChannelReceived (); + if(m_redirectToConsole) + { + SetStream(Console.OpenStandardInput(), Console.OpenStandardOutput()); + } + else + { + m_sshIO = GetStream(); + } + } + + + protected override string ChannelType + { + get { return "shell"; } + } + + public Stream IO + { + get + { +// if(m_sshIO == null) +// { +// m_sshIO = GetStream(); +// } + return m_sshIO; + } + } + + public void WriteLine(string data) + { + Write( data+"\r" ); + } + + public void Write(string data) + { + Write( Encoding.Default.GetBytes( data ) ); + } + + /// + /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. + /// + /// An array of bytes. This method copies count bytes from buffer to the current stream. + public virtual void Write(byte[] buffer) + { + Write(buffer, 0, buffer.Length); + } + + + /// + /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. + /// + /// An array of bytes. This method copies count bytes from buffer to the current stream. + /// The zero-based byte offset in buffer at which to begin copying bytes to the current stream. + /// The number of bytes to be written to the current stream. + public virtual void Write(byte[] buffer, int offset, int count) + { + IO.Write(buffer, offset, count); + IO.Flush(); + } + + /// + /// Creates a new I/O stream of communication with this SSH shell connection + /// + public Stream GetStream() + { + return new CombinedStream(m_channel.getInputStream(), m_channel.getOutputStream());; + } + + public void SetStream(Stream inputStream, Stream outputStream) + { + m_channel.setInputStream(inputStream); + m_channel.setOutputStream(outputStream); + } + + public void SetStream(Stream s) + { + SetStream(s, s); + } + + public void RedirectToConsole() + { + m_redirectToConsole = true; + } + + public virtual bool ShellOpened + { + get + { + if(m_channel != null) + { + return !m_channel.isClosed(); + } + return false; + } + } + + public virtual bool ShellConnected + { + get + { + if(m_channel != null) + { + return m_channel.isConnected(); + } + return false; + } + } + + /// + /// Gets or sets a value indicating wether this Steam sould remove the terminal emulation's escape sequence characters from the response String. + /// + public bool RemoveTerminalEmulationCharacters + { + get{return m_removeTerminalChars;} + set{m_removeTerminalChars=value;} + } + + /// + /// A regular expression pattern string to match when reading the resonse using the ReadResponse() method. The default prompt value is "\n" which makes the ReadRespons() method return one line of response. + /// + public string ExpectPattern + { + get{return m_expectPattern.ToString();} + set{m_expectPattern = new Regex(value, RegexOptions.Singleline);} + } + + /// + /// Reads a response string from the SSH channel. This method will block until the pattern in the 'Prompt' property is matched in the response. + /// + /// A response string from the SSH server. + public string Expect() + { + return Expect(m_expectPattern); + } + + /// + /// Reads a response string from the SSH channel. This method will block until the pattern in the 'Prompt' property is matched in the response. + /// + /// A response string from the SSH server. + public string Expect(string pattern) + { + return Expect( new Regex( pattern, RegexOptions.Singleline )); + } + + /// + /// Reads a response string from the SSH channel. This method will block until the pattern in the 'Prompt' property is matched in the response. + /// + /// A response string from the SSH server. + public string Expect(Regex pattern) + { + int readCount; + StringBuilder resp = new StringBuilder(); + byte[] buff = new byte[1024]; + Match match; + + do + { + readCount = IO.Read(buff, 0, buff.Length); + if(readCount == -1) break; + string tmp = System.Text.Encoding.Default.GetString(buff, 0, readCount); + resp.Append( tmp, 0, readCount); + string s = resp.ToString(); + match = pattern.Match( s ); + }while(!match.Success); + + string result = resp.ToString(); + if(RemoveTerminalEmulationCharacters) + result = HandleTerminalChars(result); + return result; + } + + /// + /// Removes escape sequence characters from the input string. + /// + public static string HandleTerminalChars(string str) + { + str = str.Replace("(B)0", ""); + str = Regex.Replace(str, escapeCharsPattern, ""); + str = str.Replace(((char)15).ToString(), ""); + str = Regex.Replace(str, ((char)27)+"=*", ""); + //str = Regex.Replace(str, "\\s*\r\n", "\r\n"); + return str; + } + } +} diff --git a/SharpSSH/SshStream.cs b/SharpSSH/SshStream.cs new file mode 100644 index 00000000..281582cd --- /dev/null +++ b/SharpSSH/SshStream.cs @@ -0,0 +1,325 @@ +using System; +using System.IO; +using System.Collections; +using Tamir.SharpSsh.jsch; +using Tamir.Streams; +using System.Text; +using System.Text.RegularExpressions; + +/* + * SshStream.cs + * + * THIS SOURCE CODE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + * KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + * PURPOSE. + * + * Copyright (C) 2005 Tamir Gal, tamirgal@myrealbox.com. + */ + +namespace Tamir.SharpSsh +{ + /// + /// A Stream based SSH class + /// + public class SshStream : Stream + { + private Stream m_in; + private Stream m_out; + private ChannelShell m_channel; + private string m_host; + private Regex m_prompt; + private string m_escapeCharPattern; + private bool m_removeTerminalChars = false; + private Session m_session; + + /// + /// Constructs a new SSH stream. + /// + /// The hostname or IP address of the remote SSH machine + /// The name of the user connecting to the remote machine + /// The password of the user connecting to the remote machine + public SshStream(string host, string username, string password) + { + this.m_host = host; + JSch jsch=new JSch(); + m_session=jsch.getSession(username, host, 22); + m_session.setPassword( password ); + + Hashtable config=new Hashtable(); + config.Add("StrictHostKeyChecking", "no"); + m_session.setConfig(config); + + m_session.connect(); + m_channel=(ChannelShell)m_session.openChannel("shell"); + + m_in = m_channel.getInputStream(); + m_out = m_channel.getOutputStream(); + + m_channel.connect(); + m_channel.setPtySize(80, 132, 1024, 768); + + Prompt = "\n"; + m_escapeCharPattern = "\\[[0-9;?]*[^0-9;]"; + } + + /// + /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. + /// + /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count- 1) replaced by the bytes read from the current source. + /// The zero-based byte offset in buffer at which to begin storing the data read from the current stream. + /// The maximum number of bytes to be read from the current stream. + /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. + public override int Read(byte[] buffer, int offset, int count) + { + return m_in.Read(buffer, offset, count); + } + + /// + /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. + /// + /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count- 1) replaced by the bytes read from the current source. + /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. + public virtual int Read(byte[] buffer) + { + return Read(buffer, 0, buffer.Length); + } + + /// + /// Reads a byte from the stream and advances the position within the stream by one byte, or returns -1 if at the end of the stream. + /// + /// The unsigned byte cast to an Int32, or -1 if at the end of the stream. + public override int ReadByte() + { + return m_in.ReadByte(); + } + + /// + /// Writes a byte to the current position in the stream and advances the position within the stream by one byte. + /// + /// The byte to write to the stream. + public override void WriteByte(byte value) + { + m_out.WriteByte(value); + } + + /// + /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. + /// + /// An array of bytes. This method copies count bytes from buffer to the current stream. + /// The zero-based byte offset in buffer at which to begin copying bytes to the current stream. + /// The number of bytes to be written to the current stream. + public override void Write(byte[] buffer, int offset, int count) + { + m_out.Write(buffer, offset, count); + } + + /// + /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. + /// + /// An array of bytes. This method copies count bytes from buffer to the current stream. + public virtual void Write(byte[] buffer) + { + Write(buffer, 0, buffer.Length); + } + + /// + /// Writes a String into the SSH channel. This method appends an 'end of line' character to the input string. + /// + /// The String to write to the SSH channel. + public void Write(String data) + { + data += "\r"; + Write( Encoding.Default.GetBytes( data ) ); + Flush(); + } + + /// + /// Closes the current stream and releases any resources (such as sockets and file handles) associated with the current stream. + /// + public override void Close() + { + try + { + base.Close (); + m_in.Close(); + m_out.Close(); + m_channel.close(); + m_channel.disconnect(); + m_session.disconnect(); + } + catch{} + } + + /// + /// Gets a value indicating whether the current stream supports reading. + /// + public override bool CanRead + { + get + { + return m_in.CanRead; + } + } + + /// + /// Gets a value indicating whether the current stream supports writing. + /// + public override bool CanWrite + { + get + { + return m_out.CanWrite; + } + } + + /// + /// Gets a value indicating whether the current stream supports seeking. This stream cannot seek, and will always return false. + /// + public override bool CanSeek + { + get + { + return false; + } + } + + /// + /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. + /// + public override void Flush() + { + m_out.Flush(); + } + + /// + /// Gets the length in bytes of the stream. + /// + public override long Length + { + get + { + return 0; + } + } + + /// + /// Gets or sets the position within the current stream. This Stream cannot seek. This property has no effect on the Stream and will always return 0. + /// + public override long Position + { + get + { + return 0; + } + set + { + } + } + + /// + /// This method has no effect on the Stream. + /// + public override void SetLength(long value) + { + } + + /// + /// This method has no effect on the Stream. + /// + public override long Seek(long offset, SeekOrigin origin) + { + return 0; + } + + /// + /// A regular expression pattern string to match when reading the resonse using the ReadResponse() method. The default prompt value is "\n" which makes the ReadRespons() method return one line of response. + /// + public string Prompt + { + get{return m_prompt.ToString();} + set{m_prompt = new Regex(value, RegexOptions.Singleline);} + } + + /// + /// Gets or sets a value indicating wether this Steam sould remove the terminal emulation's escape sequence characters from the response String. + /// + public bool RemoveTerminalEmulationCharacters + { + get{return m_removeTerminalChars;} + set{m_removeTerminalChars=value;} + } + + /// + /// Gets the Cipher algorithm name used in this SSH connection. + /// + public string Cipher + { + get{return m_session.getCipher();} + } + + /// + /// Gets the MAC algorithm name used in this SSH connection. + /// + public string Mac + { + get{return m_session.getMac();} + } + + /// + /// Gets the server SSH version string. + /// + public string ServerVersion + { + get{return m_session.getServerVersion();} + } + + /// + /// Gets the client SSH version string. + /// + public string ClientVersion + { + get{return m_session.getClientVersion();} + } + + /// + /// Reads a response string from the SSH channel. This method will block until the pattern in the 'Prompt' property is matched in the response. + /// + /// A response string from the SSH server. + public string ReadResponse() + { + int readCount; + StringBuilder resp = new StringBuilder(); + byte[] buff = new byte[1024]; + Match match; + + do + { + readCount = this.Read(buff); + resp.Append( System.Text.Encoding.Default.GetString( buff), 0, readCount); + string s = resp.ToString(); + match = m_prompt.Match( s ); + }while(!match.Success); + + return HandleTerminalChars( resp.ToString() ); + } + + /// + /// Removes escape sequence characters from the input string. + /// + /// + /// + private string HandleTerminalChars(string str) + { + if(RemoveTerminalEmulationCharacters) + { + str = str.Replace("(B)0", ""); + str = Regex.Replace(str, m_escapeCharPattern, ""); + str = str.Replace(((char)15).ToString(), ""); + str = Regex.Replace(str, ((char)27).ToString()+"=*", ""); + //str = Regex.Replace(str, "\\s*\r\n", "\r\n"); + } + return str; + } + } +} diff --git a/SharpSSH/SshTransferException.cs b/SharpSSH/SshTransferException.cs new file mode 100644 index 00000000..4ee64993 --- /dev/null +++ b/SharpSSH/SshTransferException.cs @@ -0,0 +1,44 @@ +using System; + +/* + * SshTransferException.cs + * + * Copyright (c) 2006 Tamir Gal, http://www.tamirgal.com, All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the distribution. + * + * 3. The names of the authors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR + * *OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + **/ +namespace Tamir.SharpSsh +{ + /// + /// Summary description for SshTransferException. + /// + public class SshTransferException : Exception + { + public SshTransferException(string msg):base(msg) + { + } + } +} diff --git a/SharpSSH/SshTransferProtocolBase.cs b/SharpSSH/SshTransferProtocolBase.cs new file mode 100644 index 00000000..df1a09b3 --- /dev/null +++ b/SharpSSH/SshTransferProtocolBase.cs @@ -0,0 +1,114 @@ +using System; + +/* + * SshTransferProtocolBase.cs + * + * Copyright (c) 2006 Tamir Gal, http://www.tamirgal.com, All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the distribution. + * + * 3. The names of the authors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR + * *OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + **/ +namespace Tamir.SharpSsh +{ + /// + /// Summary description for SshTransferProtocolBase. + /// + public abstract class SshTransferProtocolBase : SshBase, ITransferProtocol + { + public SshTransferProtocolBase(string host, string user, string password) + : base(host, user, password) + { + } + + public SshTransferProtocolBase(string host, string user) + : base(host, user) + { + } + #region ITransferProtocol Members + + public abstract void Get(string fromFilePath, string toFilePath); + public abstract void Put(string fromFilePath, string toFilePath); + public abstract void Mkdir(string directory); + public abstract void Cancel(); + + /// + /// Triggered when transfer is starting + /// + public event FileTransferEvent OnTransferStart; + /// + /// Triggered when transfer ends + /// + public event FileTransferEvent OnTransferEnd; + /// + /// Triggered on every interval with the transfer progress iformation. + /// + public event FileTransferEvent OnTransferProgress; + + /// + /// Sends a notification that a file transfer has started + /// + /// The source file to transferred + /// Transfer destination + /// Total bytes to transfer + /// A transfer message + protected void SendStartMessage(string src, string dst, int totalBytes, string msg) + { + if (OnTransferStart != null) + OnTransferStart(src, dst, 0, totalBytes, msg); + } + + /// + /// Sends a notification that a file transfer has ended + /// + /// The source file to transferred + /// Transfer destination + /// Transferred Bytes + /// Total bytes to transfer + /// A transfer message + protected void SendEndMessage(string src, string dst, int transferredBytes, int totalBytes, string msg) + { + if (OnTransferEnd != null) + OnTransferEnd(src, dst, transferredBytes, totalBytes, msg); + } + + /// + /// Sends a transfer progress notification + /// + /// The source file to transferred + /// Transfer destination + /// Transferred Bytes + /// Total bytes to transfer + /// A transfer message + protected void SendProgressMessage(string src, string dst, int transferredBytes, int totalBytes, string msg) + { + if (OnTransferProgress != null) + { + OnTransferProgress(src, dst, transferredBytes, totalBytes, msg); + } + } + + #endregion + } +} diff --git a/SharpSSH/Streams/CombinedStream.cs b/SharpSSH/Streams/CombinedStream.cs new file mode 100644 index 00000000..16c2039d --- /dev/null +++ b/SharpSSH/Streams/CombinedStream.cs @@ -0,0 +1,206 @@ +using System; +using System.IO; + +/* + * CombinedStream.cs + * + * Copyright (c) 2006 Tamir Gal, http://www.tamirgal.com, All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the distribution. + * + * 3. The names of the authors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR + * *OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + **/ +namespace Tamir.Streams +{ + /// + /// Summary description for CombinedStream. + /// + public class CombinedStream : Stream + { + private Stream m_in; + private Stream m_out; + + public CombinedStream(Stream inputStream, Stream outputStream) + { + this.m_in=inputStream; + this.m_out=outputStream; + } + + /// + /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. + /// + /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count- 1) replaced by the bytes read from the current source. + /// The zero-based byte offset in buffer at which to begin storing the data read from the current stream. + /// The maximum number of bytes to be read from the current stream. + /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. + public override int Read(byte[] buffer, int offset, int count) + { + return m_in.Read(buffer, offset, count); + } + + /// + /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. + /// + /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count- 1) replaced by the bytes read from the current source. + /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. + public virtual int Read(byte[] buffer) + { + return Read(buffer, 0, buffer.Length); + } + + /// + /// Reads a byte from the stream and advances the position within the stream by one byte, or returns -1 if at the end of the stream. + /// + /// The unsigned byte cast to an Int32, or -1 if at the end of the stream. + public override int ReadByte() + { + return m_in.ReadByte(); + } + + /// + /// Writes a byte to the current position in the stream and advances the position within the stream by one byte. + /// + /// The byte to write to the stream. + public override void WriteByte(byte value) + { + m_out.WriteByte(value); + } + + /// + /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. + /// + /// An array of bytes. This method copies count bytes from buffer to the current stream. + /// The zero-based byte offset in buffer at which to begin copying bytes to the current stream. + /// The number of bytes to be written to the current stream. + public override void Write(byte[] buffer, int offset, int count) + { + m_out.Write(buffer, offset, count); + } + + /// + /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. + /// + /// An array of bytes. This method copies count bytes from buffer to the current stream. + public virtual void Write(byte[] buffer) + { + Write(buffer, 0, buffer.Length); + } + + + /// + /// Closes the current stream and releases any resources (such as sockets and file handles) associated with the current stream. + /// + public override void Close() + { + try + { + base.Close (); + m_in.Close(); + m_out.Close(); + } + catch{} + } + + /// + /// Gets a value indicating whether the current stream supports reading. + /// + public override bool CanRead + { + get + { + return m_in.CanRead; + } + } + + /// + /// Gets a value indicating whether the current stream supports writing. + /// + public override bool CanWrite + { + get + { + return m_out.CanWrite; + } + } + + /// + /// Gets a value indicating whether the current stream supports seeking. This stream cannot seek, and will always return false. + /// + public override bool CanSeek + { + get + { + return false; + } + } + + /// + /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. + /// + public override void Flush() + { + m_out.Flush(); + } + + /// + /// Gets the length in bytes of the stream. + /// + public override long Length + { + get + { + return 0; + } + } + + /// + /// Gets or sets the position within the current stream. This Stream cannot seek. This property has no effect on the Stream and will always return 0. + /// + public override long Position + { + get + { + return 0; + } + set + { + } + } + + /// + /// This method has no effect on the Stream. + /// + public override void SetLength(long value) + { + } + + /// + /// This method has no effect on the Stream. + /// + public override long Seek(long offset, SeekOrigin origin) + { + return 0; + } + } +} diff --git a/SharpSSH/Streams/InputStream.cs b/SharpSSH/Streams/InputStream.cs new file mode 100644 index 00000000..07b7d221 --- /dev/null +++ b/SharpSSH/Streams/InputStream.cs @@ -0,0 +1,69 @@ +//using System; +//using System.IO; +// +//namespace Tamir.Streams +//{ +// /// +// /// Summary description for InputStream. +// /// +// public abstract class InputStream : Stream +// { +// public override void WriteByte(byte value) +// { +// } +// +// public override void Write(byte[] buffer, int offset, int count) +// { +// } +// +// public override bool CanRead +// { +// get +// { +// return true; +// } +// } +// public override bool CanWrite +// { +// get +// { +// return false; +// } +// } +// public override bool CanSeek +// { +// get +// { +// return false; +// } +// } +// public override void Flush() +// { +// +// } +// public override long Length +// { +// get +// { +// return 0; +// } +// } +// public override long Position +// { +// get +// { +// return 0; +// } +// set +// { +// } +// } +// public override void SetLength(long value) +// { +// } +// public override long Seek(long offset, SeekOrigin origin) +// { +// return 0; +// } +// } +//} diff --git a/SharpSSH/Streams/OutputStream.cs b/SharpSSH/Streams/OutputStream.cs new file mode 100644 index 00000000..7cec3d8a --- /dev/null +++ b/SharpSSH/Streams/OutputStream.cs @@ -0,0 +1,70 @@ +//using System; +//using System.IO; +// +//namespace Tamir.Streams +//{ +// /// +// /// Summary description for OutputStream. +// /// +// public abstract class OutputStream : Stream +// { +// public override int Read(byte[] buffer, int offset, int count) +// { +// return 0; +// } +// +// public override int ReadByte() +// { +// return 0; +// } +// +// public override bool CanRead +// { +// get +// { +// return false; +// } +// } +// public override bool CanWrite +// { +// get +// { +// return true; +// } +// } +// public override bool CanSeek +// { +// get +// { +// return false; +// } +// } +// public override void Flush() +// { +// } +// public override long Length +// { +// get +// { +// return 0; +// } +// } +// public override long Position +// { +// get +// { +// return 0; +// } +// set +// { +// } +// } +// public override void SetLength(long value) +// { +// } +// public override long Seek(long offset, SeekOrigin origin) +// { +// return 0; +// } +// } +//} diff --git a/SharpSSH/Streams/PipedInputStream.cs b/SharpSSH/Streams/PipedInputStream.cs new file mode 100644 index 00000000..57677388 --- /dev/null +++ b/SharpSSH/Streams/PipedInputStream.cs @@ -0,0 +1,517 @@ +using System; +using System.IO; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace Tamir.Streams +{ +/* + * @(#)PipedInputStream.java 1.35 03/12/19 + * + * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ + +/** + * A piped input stream should be connected + * to a piped output stream; the piped input + * stream then provides whatever data bytes + * are written to the piped output stream. + * Typically, data is read from a PipedInputStream + * object by one thread and data is written + * to the corresponding PipedOutputStream + * by some other thread. Attempting to use + * both objects from a single thread is not + * recommended, as it may deadlock the thread. + * The piped input stream contains a buffer, + * decoupling read operations from write operations, + * within limits. + * + * @author James Gosling + * @version 1.35, 12/19/03 + * @see java.io.PipedOutputStream + * @since JDK1.0 + */ + public class PipedInputStream : Tamir.SharpSsh.java.io.InputStream + { + internal bool closedByWriter = false; + internal volatile bool closedByReader = false; + internal bool connected = false; + + /* REMIND: identification of the read and write sides needs to be + more sophisticated. Either using thread groups (but what about + pipes within a thread?) or using finalization (but it may be a + long time until the next GC). */ + internal Thread readSide; + internal Thread writeSide; + + /** + * The size of the pipe's circular input buffer. + * @since JDK1.1 + */ + internal const int PIPE_SIZE = 1024; + + /** + * The circular buffer into which incoming data is placed. + * @since JDK1.1 + */ + internal byte[] buffer = new byte[PIPE_SIZE]; + + /** + * The index of the position in the circular buffer at which the + * next byte of data will be stored when received from the connected + * piped output stream. in<0 implies the buffer is empty, + * in==out implies the buffer is full + * @since JDK1.1 + */ + internal int m_in = -1; + + /** + * The index of the position in the circular buffer at which the next + * byte of data will be read by this piped input stream. + * @since JDK1.1 + */ + internal int m_out = 0; + + /** + * Creates a PipedInputStream so + * that it is connected to the piped output + * stream src. Data bytes written + * to src will then be available + * as input from this stream. + * + * @param src the stream to connect to. + * @exception IOException if an I/O error occurs. + */ + public PipedInputStream(PipedOutputStream src) + { + connect(src); + } + + /** + * Creates a PipedInputStream so + * that it is not yet connected. It must be + * connected to a PipedOutputStream + * before being used. + * + * @see java.io.PipedInputStream#connect(java.io.PipedOutputStream) + * @see java.io.PipedOutputStream#connect(java.io.PipedInputStream) + */ + public PipedInputStream() + { + int i = 0; + } + + /** + * Causes this piped input stream to be connected + * to the piped output stream src. + * If this object is already connected to some + * other piped output stream, an IOException + * is thrown. + *

+ * If src is an + * unconnected piped output stream and snk + * is an unconnected piped input stream, they + * may be connected by either the call: + *

+ *

snk.connect(src) 
+ *

+ * or the call: + *

+ *

src.connect(snk) 
+ *

+ * The two + * calls have the same effect. + * + * @param src The piped output stream to connect to. + * @exception IOException if an I/O error occurs. + */ + public virtual void connect(PipedOutputStream src) + { + src.connect(this); + } + + /** + * Receives a byte of data. This method will block if no input is + * available. + * @param b the byte being received + * @exception IOException If the pipe is broken. + * @since JDK1.1 + */ + [MethodImpl(MethodImplOptions.Synchronized)] + internal void receive(int b) + { + checkStateForReceive(); + writeSide = Thread.CurrentThread; + if (m_in == m_out) + awaitSpace(); + if (m_in < 0) + { + m_in = 0; + m_out = 0; + } + buffer[m_in++] = (byte)(b & 0xFF); + if (m_in >= buffer.Length) + { + m_in = 0; + } + } + + /** + * Receives data into an array of bytes. This method will + * block until some input is available. + * @param b the buffer into which the data is received + * @param off the start offset of the data + * @param len the maximum number of bytes received + * @exception IOException If an I/O error has occurred. + */ + [MethodImpl(MethodImplOptions.Synchronized)] + internal void receive(byte[] b, int off, int len) + { + checkStateForReceive(); + writeSide = Thread.CurrentThread; + int bytesToTransfer = len; + while (bytesToTransfer > 0) + { + if (m_in == m_out) + awaitSpace(); + int nextTransferAmount = 0; + if (m_out < m_in) + { + nextTransferAmount = buffer.Length - m_in; + } + else if (m_in < m_out) + { + if (m_in == -1) + { + m_in = m_out = 0; + nextTransferAmount = buffer.Length - m_in; + } + else + { + nextTransferAmount = m_out - m_in; + } + } + if (nextTransferAmount > bytesToTransfer) + nextTransferAmount = bytesToTransfer; + assert(nextTransferAmount > 0); + Array.Copy(b, off, buffer, m_in, nextTransferAmount); + bytesToTransfer -= nextTransferAmount; + off += nextTransferAmount; + m_in += nextTransferAmount; + if (m_in >= buffer.Length) + { + m_in = 0; + } + } + } + + private void checkStateForReceive() + { + if (!connected) + { + throw new IOException("Pipe not connected"); + } + else if (closedByWriter || closedByReader) + { + throw new IOException("Pipe closed"); + } + else if (readSide != null && !readSide.IsAlive) + { + throw new IOException("Read end dead"); + } + } + + private void awaitSpace() + { + while (m_in == m_out) + { + if ((readSide != null) && !readSide.IsAlive) + { + throw new IOException("Pipe broken"); + } + /* full: kick any waiting readers */ + //java: notifyAll(); + Monitor.PulseAll(this); + try + { + //java: wait(1000); + Monitor.Wait(this, 1000); + } + catch (ThreadInterruptedException ex) + { + throw ex; + } + } + } + + /** + * Notifies all waiting threads that the last byte of data has been + * received. + */ + [MethodImpl(MethodImplOptions.Synchronized)] + internal void receivedLast() + { + closedByWriter = true; + //notifyAll(); + Monitor.PulseAll(this); + } + + /** + * Reads the next byte of data from this piped input stream. The + * value byte is returned as an int in the range + * 0 to 255. If no byte is available + * because the end of the stream has been reached, the value + * -1 is returned. This method blocks until input data + * is available, the end of the stream is detected, or an exception + * is thrown. + * If a thread was providing data bytes + * to the connected piped output stream, but + * the thread is no longer alive, then an + * IOException is thrown. + * + * @return the next byte of data, or -1 if the end of the + * stream is reached. + * @exception IOException if the pipe is broken. + */ + [MethodImpl(MethodImplOptions.Synchronized)] + public virtual int read() + { + if (!connected) + { + throw new IOException("Pipe not connected"); + } + else if (closedByReader) + { + throw new IOException("Pipe closed"); + } + else if (writeSide != null && !writeSide.IsAlive + && !closedByWriter && (m_in < 0)) + { + throw new IOException("Write end dead"); + } + + readSide = Thread.CurrentThread; + int trials = 2; + while (m_in < 0) + { + if (closedByWriter) + { + /* closed by writer, return EOF */ + return -1; + } + if ((writeSide != null) && (!writeSide.IsAlive) && (--trials < 0)) + { + throw new IOException("Pipe broken"); + } + /* might be a writer waiting */ + Monitor.PulseAll(this); + try + { + Monitor.Wait(this, 1000); + } + catch (ThreadInterruptedException ex) + { + throw ex; + } + } + int ret = buffer[m_out++] & 0xFF; + if (m_out >= buffer.Length) + { + m_out = 0; + } + if (m_in == m_out) + { + /* now empty */ + m_in = -1; + } + return ret; + } + + /** + * Reads up to len bytes of data from this piped input + * stream into an array of bytes. Less than len bytes + * will be read if the end of the data stream is reached. This method + * blocks until at least one byte of input is available. + * If a thread was providing data bytes + * to the connected piped output stream, but + * the thread is no longer alive, then an + * IOException is thrown. + * + * @param b the buffer into which the data is read. + * @param off the start offset of the data. + * @param len the maximum number of bytes read. + * @return the total number of bytes read into the buffer, or + * -1 if there is no more data because the end of + * the stream has been reached. + * @exception IOException if an I/O error occurs. + */ + [MethodImpl(MethodImplOptions.Synchronized)] + public override int read(byte[] b, int off, int len) + { + if (b == null) + { + throw new NullReferenceException(); + } + else if ((off < 0) || (off > b.Length) || (len < 0) || + ((off + len) > b.Length) || ((off + len) < 0)) + { + throw new IndexOutOfRangeException(); + } + else if (len == 0) + { + return 0; + } + + /* possibly wait on the first character */ + int c = read(); + if (c < 0) + { + return -1; + } + b[off] = (byte) c; + int rlen = 1; + while ((m_in >= 0) && (--len > 0)) + { + b[off + rlen] = buffer[m_out++]; + rlen++; + if (m_out >= buffer.Length) + { + m_out = 0; + } + if (m_in == m_out) + { + /* now empty */ + m_in = -1; + } + } + return rlen; + } + + /** + * Returns the number of bytes that can be read from this input + * stream without blocking. This method overrides the available + * method of the parent class. + * + * @return the number of bytes that can be read from this input stream + * without blocking. + * @exception IOException if an I/O error occurs. + * @since JDK1.0.2 + */ + [MethodImpl(MethodImplOptions.Synchronized)] + public virtual int available() + { + if(m_in < 0) + return 0; + else if(m_in == m_out) + return buffer.Length; + else if (m_in > m_out) + return m_in - m_out; + else + return m_in + buffer.Length - m_out; + } + + /** + * Closes this piped input stream and releases any system resources + * associated with the stream. + * + * @exception IOException if an I/O error occurs. + */ + public override void close() + { + closedByReader = true; + lock (this) + { + m_in = -1; + } + } + + private void assert(bool exp) + { + if (!exp) + throw new Exception("Assertion failed!"); + } + + /////////////////////////////////////// + + + public override int Read(byte[] buffer, int offset, int count) + { + return this.read(buffer, offset, count); + } + + public override int ReadByte() + { + return this.read(); + } + + public override void WriteByte(byte value) + { + } + + public override void Write(byte[] buffer, int offset, int count) + { + } + public override void Close() + { + base.Close (); + this.close(); + } + public override bool CanRead + { + get + { + return true; + } + } + public override bool CanWrite + { + get + { + return false; + } + } + public override bool CanSeek + { + get + { + return false; + } + } + public override void Flush() + { + + } + public override long Length + { + get + { + if(m_in > m_out) + return (m_in - m_out); + else + { + return (buffer.Length -m_out+m_in); + } + } + } + public override long Position + { + get + { + return m_out; + } + set + { + throw new IOException("Setting the position of this stream is not supported"); + } + } + public override void SetLength(long value) + { + throw new IOException("Setting the length of this stream is not supported"); + } + public override long Seek(long offset, SeekOrigin origin) + { + return 0; + } + } +} diff --git a/SharpSSH/Streams/PipedOutputStream.cs b/SharpSSH/Streams/PipedOutputStream.cs new file mode 100644 index 00000000..632c7fb5 --- /dev/null +++ b/SharpSSH/Streams/PipedOutputStream.cs @@ -0,0 +1,276 @@ +using System; +using System.IO; +using System.Runtime.CompilerServices; + +namespace Tamir.Streams +{ + /* + * @(#)PipedOutputStream.java 1.26 03/12/19 + * + * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ + + /** + * A piped output stream can be connected to a piped input stream + * to create a communications pipe. The piped output stream is the + * sending end of the pipe. Typically, data is written to a + * PipedOutputStream object by one thread and data is + * read from the connected PipedInputStream by some + * other thread. Attempting to use both objects from a single thread + * is not recommended as it may deadlock the thread. + * + * @author James Gosling + * @version 1.26, 12/19/03 + * @see java.io.PipedInputStream + * @since JDK1.0 + */ + public class PipedOutputStream : Tamir.SharpSsh.java.io.OutputStream + { + + /* REMIND: identification of the read and write sides needs to be + more sophisticated. Either using thread groups (but what about + pipes within a thread?) or using finalization (but it may be a + long time until the next GC). */ + private PipedInputStream sink; + + /** + * Creates a piped output stream connected to the specified piped + * input stream. Data bytes written to this stream will then be + * available as input from snk. + * + * @param snk The piped input stream to connect to. + * @exception IOException if an I/O error occurs. + */ + public PipedOutputStream(PipedInputStream snk) + { + connect(snk); + } + + /** + * Creates a piped output stream that is not yet connected to a + * piped input stream. It must be connected to a piped input stream, + * either by the receiver or the sender, before being used. + * + * @see java.io.PipedInputStream#connect(java.io.PipedOutputStream) + * @see java.io.PipedOutputStream#connect(java.io.PipedInputStream) + */ + public PipedOutputStream() + { + } + + /** + * Connects this piped output stream to a receiver. If this object + * is already connected to some other piped input stream, an + * IOException is thrown. + *

+ * If snk is an unconnected piped input stream and + * src is an unconnected piped output stream, they may + * be connected by either the call: + *

+		 * src.connect(snk)
+ * or the call: + *
+		 * snk.connect(src)
+ * The two calls have the same effect. + * + * @param snk the piped input stream to connect to. + * @exception IOException if an I/O error occurs. + */ + [MethodImpl(MethodImplOptions.Synchronized)] + public virtual void connect(PipedInputStream snk) + { + if (snk == null) + { + throw new NullReferenceException(); + } + else if (sink != null || snk.connected) + { + throw new IOException("Already connected"); + } + sink = snk; + snk.m_in = -1; + snk.m_out = 0; + snk.connected = true; + int t=0; + } + + /** + * Writes the specified byte to the piped output stream. + * If a thread was reading data bytes from the connected piped input + * stream, but the thread is no longer alive, then an + * IOException is thrown. + *

+ * Implements the write method of OutputStream. + * + * @param b the byte to be written. + * @exception IOException if an I/O error occurs. + */ + public virtual void write(int b) + { + if (sink == null) + { + throw new IOException("Pipe not connected"); + } + sink.receive(b); + } + + /** + * Writes len bytes from the specified byte array + * starting at offset off to this piped output stream. + * If a thread was reading data bytes from the connected piped input + * stream, but the thread is no longer alive, then an + * IOException is thrown. + * + * @param b the data. + * @param off the start offset in the data. + * @param len the number of bytes to write. + * @exception IOException if an I/O error occurs. + */ + public override void write(byte[] b, int off, int len) + { + if (sink == null) + { + throw new IOException("Pipe not connected"); + } + else if (b == null) + { + throw new NullReferenceException(); + } + else if ((off < 0) || (off > b.Length) || (len < 0) || + ((off + len) > b.Length) || ((off + len) < 0)) + { + throw new IndexOutOfRangeException(); + } + else if (len == 0) + { + return; + } + sink.receive(b, off, len); + } + + public virtual void write(byte[] b) + { + write(b, 0, b.Length); + } + + /** + * Flushes this output stream and forces any buffered output bytes + * to be written out. + * This will notify any readers that bytes are waiting in the pipe. + * + * @exception IOException if an I/O error occurs. + */ + [MethodImpl(MethodImplOptions.Synchronized)] + public override void flush() + { + if (sink != null) + { + lock (sink) + { + //sink.notifyAll(); + System.Threading.Monitor.PulseAll(sink); + } + } + } + + /** + * Closes this piped output stream and releases any system resources + * associated with this stream. This stream may no longer be used for + * writing bytes. + * + * @exception IOException if an I/O error occurs. + */ + public override void close() + { + if (sink != null) + { + sink.receivedLast(); + } + } + + /////////////////////////////////////// + + + public override int Read(byte[] buffer, int offset, int count) + { + return 0; + } + + public override int ReadByte() + { + return 0; + } + + public override void WriteByte(byte value) + { + this.write(value); + } + + public override void Write(byte[] buffer, int offset, int count) + { + this.write(buffer, offset, count); + } + public virtual void Write(byte[] buffer) + { + this.Write(buffer, 0, buffer.Length); + } + public override void Close() + { + base.Close (); + this.close(); + } + public override bool CanRead + { + get + { + return false; + } + } + public override bool CanWrite + { + get + { + return true; + } + } + public override bool CanSeek + { + get + { + return false; + } + } + public override void Flush() + { + this.flush(); + } + public override long Length + { + get + { + return sink.Length; + } + } + public override long Position + { + get + { + return sink.m_in; + } + set + { + throw new IOException("Setting the position of this stream is not supported"); + } + } + public override void SetLength(long value) + { + throw new IOException("Setting the length of this stream is not supported"); + } + public override long Seek(long offset, SeekOrigin origin) + { + return 0; + } + } + +} diff --git a/SharpSSH/Streams/PipedStream.cs b/SharpSSH/Streams/PipedStream.cs new file mode 100644 index 00000000..f6129854 --- /dev/null +++ b/SharpSSH/Streams/PipedStream.cs @@ -0,0 +1,97 @@ +using System; +using System.IO; + +namespace Tamir.Streams +{ + ///

+ /// Summary description for PipedStream. + /// + public class PipedStream : Stream + { + PipedInputStream pins; + PipedOutputStream pouts; + + public PipedStream(PipedInputStream pins, PipedOutputStream pouts) + { + this.pins = pins; + this.pouts = pouts; + } + + public override int Read(byte[] buffer, int offset, int count) + { + return pins.read(buffer, offset, count); + } + + public override int ReadByte() + { + return pins.read(); + } + + public override void WriteByte(byte value) + { + pouts.write(value); + } + + + public override void Write(byte[] buffer, int offset, int count) + { + pouts.write(buffer, offset, count); + } + public override void Close() + { + base.Close (); + pins.close(); + pouts.close(); + } + public override bool CanRead + { + get + { + return true; + } + } + public override bool CanWrite + { + get + { + return true; + } + } + public override bool CanSeek + { + get + { + return false; + } + } + public override void Flush() + { + + } + public override long Length + { + get + { + return 0; + } + } + public override long Position + { + get + { + return 0; + } + set + { + } + } + public override void SetLength(long value) + { + + } + public override long Seek(long offset, SeekOrigin origin) + { + return 0; + } + } +} diff --git a/SharpSSH/Streams/ProtectedConsoleStream.cs b/SharpSSH/Streams/ProtectedConsoleStream.cs new file mode 100644 index 00000000..c397434f --- /dev/null +++ b/SharpSSH/Streams/ProtectedConsoleStream.cs @@ -0,0 +1,174 @@ +using System; +using System.IO; + +/* + * ProtectedConsoleStream.cs + * + * Copyright (c) 2006 Tamir Gal, http://www.tamirgal.com, All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the distribution. + * + * 3. The names of the authors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR + * *OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + **/ +namespace Tamir.Streams +{ + /// + /// This class provide access to the console stream obtained by calling the + /// Console.OpenStandardInput() and Console.OpenStandardOutput(), and prevents reading + /// into buffers to large for the Console Stream + /// + public class ProtectedConsoleStream : System.IO.Stream + { + Stream s; + public ProtectedConsoleStream(Stream s) + { + if((s.GetType() != Type.GetType("System.IO.__ConsoleStream"))&& + (s.GetType() != Type.GetType("System.IO.FileStream")))//for mono + { + throw new ArgumentException("Not ConsoleStream"); + } + this.s=s; + } + +// public static Stream Protect(Stream s) +// { +// if(s.GetType() == Console. +// } + + public override int Read(byte[] buffer, int offset, int count) + { + if(count > 256) + count = 256; + return s.Read(buffer, offset, count); + } + + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + return s.BeginRead (buffer, offset, count, callback, state); + } + + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + return s.BeginWrite (buffer, offset, count, callback, state); + } + + public override bool CanRead + { + get + { + return s.CanRead; + } + } + + public override bool CanSeek + { + get + { + return s.CanSeek; + } + } + public override bool CanWrite + { + get + { + return s.CanWrite; + } + } + public override void Close() + { + s.Close (); + } + public override System.Runtime.Remoting.ObjRef CreateObjRef(Type requestedType) + { + return s.CreateObjRef (requestedType); + } + public override int EndRead(IAsyncResult asyncResult) + { + return s.EndRead (asyncResult); + } + public override void EndWrite(IAsyncResult asyncResult) + { + s.EndWrite (asyncResult); + } + public override bool Equals(object obj) + { + return s.Equals (obj); + } + public override void Flush() + { + s.Flush(); + } + public override int GetHashCode() + { + return s.GetHashCode (); + } + public override object InitializeLifetimeService() + { + return s.InitializeLifetimeService (); + } + public override long Length + { + get + { + return s.Length; + } + } + public override long Position + { + get + { + return s.Position; + } + set + { + s.Position = value; + } + } + public override int ReadByte() + { + return s.ReadByte (); + } + public override long Seek(long offset, SeekOrigin origin) + { + return s.Seek(offset, origin); + } + public override void SetLength(long value) + { + s.SetLength(value); + } + public override string ToString() + { + return s.ToString (); + } + public override void Write(byte[] buffer, int offset, int count) + { + s.Write(buffer, offset, count); + } + public override void WriteByte(byte value) + { + s.WriteByte (value); + } + + } +} diff --git a/SharpSSH/java/Exception.cs b/SharpSSH/java/Exception.cs new file mode 100644 index 00000000..a184580b --- /dev/null +++ b/SharpSSH/java/Exception.cs @@ -0,0 +1,22 @@ +using Ex = System.Exception; + +namespace Tamir.SharpSsh.java +{ + /// + /// Summary description for Exception. + /// + public class Exception : Ex + { + public Exception() : base() + { + } + public Exception(string msg) : base(msg) + { + } + + public virtual string toString() + { + return ToString(); + } + } +} diff --git a/SharpSSH/java/Platform.cs b/SharpSSH/java/Platform.cs new file mode 100644 index 00000000..2753f61a --- /dev/null +++ b/SharpSSH/java/Platform.cs @@ -0,0 +1,18 @@ +using System; + +namespace Tamir.SharpSsh.java +{ + /// + /// Summary description for Platform. + /// + public class Platform + { + public static bool Windows + { + get + { + return Environment.OSVersion.Platform.ToString().StartsWith("Win"); + } + } + } +} diff --git a/SharpSSH/java/RuntimeException.cs b/SharpSSH/java/RuntimeException.cs new file mode 100644 index 00000000..f96f7217 --- /dev/null +++ b/SharpSSH/java/RuntimeException.cs @@ -0,0 +1,17 @@ +using System; + +namespace Tamir.SharpSsh.java +{ + /// + /// Summary description for RuntimeException. + /// + public class RuntimeException : Exception + { + public RuntimeException() + { + // + // TODO: Add constructor logic here + // + } + } +} diff --git a/SharpSSH/java/String.cs b/SharpSSH/java/String.cs new file mode 100644 index 00000000..61225c4c --- /dev/null +++ b/SharpSSH/java/String.cs @@ -0,0 +1,177 @@ +using System; +using Text = System.Text; +using Str = System.String; + +namespace Tamir.SharpSsh.java +{ + /// + /// Summary description for String. + /// + public class String + { + string s; + public String(string s) + { + this.s=s; + } + + public String(object o):this(o.ToString()) + { + } + + public String(byte[] arr):this(getString(arr)) + { + } + + public String(byte[] arr, int offset, int len):this(getString(arr, offset, len)) + { + } + + public static implicit operator String (string s1) + { + if(s1==null) return null; + return new String(s1); + } + + public static implicit operator Str (String s1) + { + if(s1==null) return null; + return s1.ToString(); + } + + public static Tamir.SharpSsh.java.String operator+(Tamir.SharpSsh.java.String s1, Tamir.SharpSsh.java.String s2) + { + return new Tamir.SharpSsh.java.String(s1.ToString()+s2.ToString()); + } + + public byte[] getBytes() + { + return String.getBytes(this); + } + + public override string ToString() + { + return s; + } + + public String toLowerCase() + { + return this.ToString().ToLower(); + } + + public bool startsWith(string prefix) + { + return this.ToString().StartsWith(prefix); + } + + public int indexOf(string sub) + { + return this.ToString().IndexOf(sub); + } + + public int indexOf(char sub) + { + return this.ToString().IndexOf(sub); + } + + public int indexOf(char sub, int i) + { + return this.ToString().IndexOf(sub, i); + } + + public char charAt(int i) + { + return s[i]; + } + + public String substring(int start, int end) + { + int len = end - start; + return this.ToString().Substring(start, len); + } + + public String subString(int start, int len) + { + return substring(start, len); + } + + public String substring(int len) + { + return this.ToString().Substring(len); + } + + public String subString(int len) + { + return substring(len); + } + + public int Length() + { + return this.ToString().Length; + } + + public int length() + { + return Length(); + } + + public bool endsWith(string str) + { + return s.EndsWith(str); + } + + public int lastIndexOf(string str) + { + return s.LastIndexOf(str); + } + + public int lastIndexOf(char c) + { + return s.LastIndexOf(c); + } + + public bool equals(object o) + { + return this.ToString().Equals(o.ToString()); + } + + public override bool Equals(object obj) + { + return this.equals (obj); + } + + public override int GetHashCode() + { + return s.GetHashCode (); + } + + public static string getString(byte[] arr) + { + return getString(arr, 0, arr.Length); + } + + public static string getString(byte[] arr, int offset, int len) + { + return Text.Encoding.Default.GetString(arr, offset, len); + } + + public static string getStringUTF8(byte[] arr) + { + return getStringUTF8(arr, 0, arr.Length); + } + + public static string getStringUTF8(byte[] arr, int offset, int len) + { + return Text.Encoding.UTF8.GetString(arr, offset, len); + } + + public static byte[] getBytes(string str) + { + return getBytesUTF8( str ); + } + public static byte[] getBytesUTF8(string str) + { + return Text.Encoding.UTF8.GetBytes( str ); + } + } +} diff --git a/SharpSSH/java/System.cs b/SharpSSH/java/System.cs new file mode 100644 index 00000000..c71c2f5a --- /dev/null +++ b/SharpSSH/java/System.cs @@ -0,0 +1,43 @@ +using System; + +namespace Tamir.SharpSsh.java +{ + /// + /// Summary description for System. + /// + public class System + { + public static Out Out = new Out(); + public static Err err = new Err(); + public static void arraycopy(Array a1, long sourceIndex, Array a2, long destIndex, long len) + { + Array.Copy(a1, sourceIndex, a2, destIndex, len); + } + } + + public class Out + { + public void print(string v) + { + Console.Write(v); + } + + public void println(string v) + { + Console.WriteLine(v); + } + } + + public class Err + { + public void print(string v) + { + Console.Error.Write(v); + } + + public void println(string v) + { + Console.Error.WriteLine(v); + } + } +} diff --git a/SharpSSH/java/io/File.cs b/SharpSSH/java/io/File.cs new file mode 100644 index 00000000..ea7489d5 --- /dev/null +++ b/SharpSSH/java/io/File.cs @@ -0,0 +1,71 @@ +using System; +using System.IO; + +namespace Tamir.SharpSsh.java.io +{ + /// + /// Summary description for File. + /// + public class File + { + string file; + internal FileInfo info; + + public File(string file) + { + this.file = file; + info = new FileInfo(file); + } + + public string getCanonicalPath() + { + return Path.GetFullPath(file); + } + + public bool isDirectory() + { + return Directory.Exists(file); + } + + public long Length() + { + return info.Length; + } + + public long length() + { + return Length(); + } + + public bool isAbsolute() + { + return Path.IsPathRooted(file); + } + + public java.String[] list() + { + string [] dirs = Directory.GetDirectories(file); + string [] files = Directory.GetFiles(file); + java.String[] _list = new java.String[dirs.Length+files.Length]; + System.arraycopy(dirs, 0, _list, 0, dirs.Length); + System.arraycopy(files, 0, _list, dirs.Length, files.Length); + return _list; + } + + public static string separator + { + get + { + return Path.DirectorySeparatorChar.ToString(); + } + } + + public static char separatorChar + { + get + { + return Path.DirectorySeparatorChar; + } + } + } +} diff --git a/SharpSSH/java/io/FileInputStream.cs b/SharpSSH/java/io/FileInputStream.cs new file mode 100644 index 00000000..6a4eab22 --- /dev/null +++ b/SharpSSH/java/io/FileInputStream.cs @@ -0,0 +1,45 @@ +using System; +using IO = System.IO; + +namespace Tamir.SharpSsh.java.io +{ + /// + /// Summary description for FileInputStream. + /// + public class FileInputStream : InputStream + { + IO.FileStream fs; + public FileInputStream(string file) + { + fs = IO.File.OpenRead(file); + } + + public FileInputStream(File file):this(file.info.Name) + { + } + + public override void Close() + { + fs.Close(); + } + + + public override int Read(byte[] buffer, int offset, int count) + { + return fs.Read(buffer, offset, count); + } + + public override bool CanSeek + { + get + { + return fs.CanSeek; + } + } + + public override long Seek(long offset, IO.SeekOrigin origin) + { + return fs.Seek(offset, origin); + } + } +} diff --git a/SharpSSH/java/io/FileOutputStream.cs b/SharpSSH/java/io/FileOutputStream.cs new file mode 100644 index 00000000..a9c17f6a --- /dev/null +++ b/SharpSSH/java/io/FileOutputStream.cs @@ -0,0 +1,60 @@ +using System; +using IO = System.IO; + +namespace Tamir.SharpSsh.java.io +{ + /// + /// Summary description for FileInputStream. + /// + public class FileOutputStream : OutputStream + { + IO.FileStream fs; + public FileOutputStream(string file):this(file, false) + { + } + + public FileOutputStream(File file):this(file.info.Name, false) + { + } + + public FileOutputStream(string file, bool append) + { + if(append) + fs = new IO.FileStream(file, IO.FileMode.Append); // append + else + fs = new IO.FileStream(file, IO.FileMode.Create); + } + + public FileOutputStream(File file, bool append):this(file.info.Name) + { + } + + public override void Write(byte[] buffer, int offset, int count) + { + fs.Write(buffer, offset, count); + } + + public override void Flush() + { + fs.Flush(); + } + + public override void Close() + { + fs.Close(); + } + + public override bool CanSeek + { + get + { + return fs.CanSeek; + } + } + + public override long Seek(long offset, IO.SeekOrigin origin) + { + return fs.Seek(offset, origin); + } + } +} diff --git a/SharpSSH/java/io/InputStream.cs b/SharpSSH/java/io/InputStream.cs new file mode 100644 index 00000000..75e18238 --- /dev/null +++ b/SharpSSH/java/io/InputStream.cs @@ -0,0 +1,110 @@ +using System; +using IO = System.IO; + +namespace Tamir.SharpSsh.java.io +{ + /// + /// Summary description for InputStream. + /// + public abstract class InputStream : IO.Stream + { + public virtual int read(byte[] buffer, int offset, int count) + { + return this.Read(buffer, offset, count); + } + + public virtual int read(byte[] buffer) + { + return this.Read(buffer, 0, buffer.Length); + } + + public virtual int read() + { + return this.ReadByte(); + } + + public virtual void close() + { + Close(); + } + + public override void WriteByte(byte value) + { + } + + public override void Write(byte[] buffer, int offset, int count) + { + } + + public override bool CanRead + { + get + { + return true; + } + } + public override bool CanWrite + { + get + { + return false; + } + } + public override bool CanSeek + { + get + { + return false; + } + } + public override void Flush() + { + + } + public override long Length + { + get + { + return 0; + } + } + public override long Position + { + get + { + return 0; + } + set + { + } + } + public override void SetLength(long value) + { + } + public override long Seek(long offset, IO.SeekOrigin origin) + { + return 0; + } + + public long skip(long len) + { + //Seek doesn't work + //return Seek(offset, IO.SeekOrigin.Current); + int i=0; + int count = 0; + byte[] buf = new byte[len]; + while(len>0) + { + i=Read(buf, count, (int)len);//tamir: possible lost of pressision + if(i<=0) + { + throw new Exception("inputstream is closed"); + //return (s-foo)==0 ? i : s-foo; + } + count+=i; + len-=i; + } + return count; + } + } +} diff --git a/SharpSSH/java/io/InputStreamWrapper.cs b/SharpSSH/java/io/InputStreamWrapper.cs new file mode 100644 index 00000000..4952505e --- /dev/null +++ b/SharpSSH/java/io/InputStreamWrapper.cs @@ -0,0 +1,21 @@ +using Tamir.SharpSsh.java.io; + +namespace Tamir.Streams +{ + /// + /// Summary description for InputStreamWrapper. + /// + public class InputStreamWrapper : InputStream + { + System.IO.Stream s; + public InputStreamWrapper(System.IO.Stream s) + { + this.s = s; + } + + public override int Read(byte[] buffer, int offset, int count) + { + return s.Read(buffer, offset, count); + } + } +} diff --git a/SharpSSH/java/io/JStream.cs b/SharpSSH/java/io/JStream.cs new file mode 100644 index 00000000..74124313 --- /dev/null +++ b/SharpSSH/java/io/JStream.cs @@ -0,0 +1,156 @@ +using System; +using IO = System.IO; + +namespace Tamir.SharpSsh.java.io +{ + /// + /// Summary description for Stream. + /// + public class JStream : IO.Stream + { + internal IO.Stream s; + public JStream(IO.Stream s) + { + this.s = s; + } + + public override int Read(byte[] buffer, int offset, int count) + { + return s.Read(buffer, offset, count); + } + + public override int ReadByte() + { + return s.ReadByte(); + } + + public int read(byte[] buffer, int offset, int count) + { + return Read(buffer, offset, count); + } + + public int read(byte[] buffer) + { + return Read(buffer, 0, buffer.Length); + } + + public int read() + { + return ReadByte(); + } + + public void close() + { + this.Close(); + } + + public override void Close() + { + s.Close (); + } + + public override void WriteByte(byte value) + { + s.WriteByte(value); + } + + public override void Write(byte[] buffer, int offset, int count) + { + s.Write(buffer, offset, count); + } + + public void write(byte[] buffer, int offset, int count) + { + Write(buffer, offset, count); + } + + public void write(byte[] buffer) + { + Write(buffer, 0, buffer.Length); + } + + public override bool CanRead + { + get {return s.CanRead;} + } + public override bool CanWrite + { + get + { + return s.CanWrite; + } + } + public override bool CanSeek + { + get + { + return s.CanSeek; + } + } + public override void Flush() + { + s.Flush(); + } + public override long Length + { + get + { + return s.Length; + } + } + public override long Position + { + get + { + return s.Position; + } + set + { + s.Position = value; + } + } + public override void SetLength(long value) + { + s.SetLength(value); + } + public override long Seek(long offset, IO.SeekOrigin origin) + { + return s.Seek(offset, origin); + } + + public long skip(long len) + { + //Seek doesn't work + //return Seek(offset, IO.SeekOrigin.Current); + int i=0; + int count = 0; + byte[] buf = new byte[len]; + while(len>0) + { + i=Read(buf, count, (int)len);//tamir: possible lost of pressision + if(i<=0) + { + throw new Exception("inputstream is closed"); + //return (s-foo)==0 ? i : s-foo; + } + count+=i; + len-=i; + } + return count; + } + + public int available() + { + if(s is Tamir.Streams.PipedInputStream) + { + return ((Tamir.Streams.PipedInputStream)s).available(); + } + throw new Exception("JStream.available() -- Method not implemented"); + } + + public void flush() + { + s.Flush(); + } + } +} diff --git a/SharpSSH/java/io/OutputStream.cs b/SharpSSH/java/io/OutputStream.cs new file mode 100644 index 00000000..a8e8ed6f --- /dev/null +++ b/SharpSSH/java/io/OutputStream.cs @@ -0,0 +1,85 @@ +using System; +using System.IO; + +namespace Tamir.SharpSsh.java.io +{ + /// + /// Summary description for InputStream. + /// + public abstract class OutputStream : Stream + { + public override int Read(byte[] buffer, int offset, int count) + { + return 0; + } + + public override int ReadByte() + { + return 0; + } + + public virtual void write(byte[] buffer, int offset, int count) + { + Write(buffer, offset, count); + } + + public virtual void close() + { + Close(); + } + + public virtual void flush() + { + Flush(); + } + + public override bool CanRead + { + get + { + return false; + } + } + public override bool CanWrite + { + get + { + return true; + } + } + public override bool CanSeek + { + get + { + return false; + } + } + public override void Flush() + { + } + public override long Length + { + get + { + return 0; + } + } + public override long Position + { + get + { + return 0; + } + set + { + } + } + public override void SetLength(long value) + { + } + public override long Seek(long offset, SeekOrigin origin) + { + return 0; + } + } +} diff --git a/SharpSSH/java/lang/Class.cs b/SharpSSH/java/lang/Class.cs new file mode 100644 index 00000000..2de93eea --- /dev/null +++ b/SharpSSH/java/lang/Class.cs @@ -0,0 +1,28 @@ +using System; + +namespace Tamir.SharpSsh.java.lang +{ + /// + /// Summary description for Class. + /// + public class Class + { + Type t; + private Class(Type t) + { + this.t=t; + } + private Class(string typeName) : this(Type.GetType(typeName)) + { + } + public static Class forName(string name) + { + return new Class(name); + } + + public object newInstance() + { + return Activator.CreateInstance(t); + } + } +} diff --git a/SharpSSH/java/lang/Integer.cs b/SharpSSH/java/lang/Integer.cs new file mode 100644 index 00000000..d9411817 --- /dev/null +++ b/SharpSSH/java/lang/Integer.cs @@ -0,0 +1,24 @@ +using System; + +namespace Tamir.SharpSsh.java.lang +{ + /// + /// Summary description for Integer. + /// + public class Integer + { + private int i; + public Integer(int i) + { + this.i = i; + } + public int intValue() + { + return i; + } + public static int parseInt(string s) + { + return int.Parse(s); + } + } +} diff --git a/SharpSSH/java/lang/Runnable.cs b/SharpSSH/java/lang/Runnable.cs new file mode 100644 index 00000000..3646097c --- /dev/null +++ b/SharpSSH/java/lang/Runnable.cs @@ -0,0 +1,12 @@ +using System; + +namespace Tamir.SharpSsh.java.lang +{ + /// + /// Summary description for Runnable. + /// + public interface Runnable + { + void run(); + } +} diff --git a/SharpSSH/java/lang/StringBuffer.cs b/SharpSSH/java/lang/StringBuffer.cs new file mode 100644 index 00000000..fbf6c5cd --- /dev/null +++ b/SharpSSH/java/lang/StringBuffer.cs @@ -0,0 +1,64 @@ +using System; +using System.Text; + +namespace Tamir.SharpSsh.java.lang +{ + /// + /// Summary description for StringBuffer. + /// + public class StringBuffer + { + StringBuilder sb; + public StringBuffer() + { + sb = new StringBuilder(); + } + + public StringBuffer(string s) + { + sb = new StringBuilder(s); + } + + public StringBuffer(StringBuilder sb):this(sb.ToString()) + { + } + + public StringBuffer(Tamir.SharpSsh.java.String s):this(s.ToString()) + { + } + + public StringBuffer append(string s) + { + sb.Append(s); + return this; + } + + public StringBuffer append(char s) + { + sb.Append(s); + return this; + } + + public StringBuffer append(Tamir.SharpSsh.java.String s) + { + return append(s.ToString()); + } + + public StringBuffer delete(int start, int end) + { + sb.Remove(start, end-start); + return this; + } + + public override string ToString() + { + return sb.ToString(); + } + + public string toString() + { + return ToString(); + } + + } +} diff --git a/SharpSSH/java/lang/Thread.cs b/SharpSSH/java/lang/Thread.cs new file mode 100644 index 00000000..34489562 --- /dev/null +++ b/SharpSSH/java/lang/Thread.cs @@ -0,0 +1,75 @@ +using System; +using System.Threading; +using Threading = System.Threading; + +namespace Tamir.SharpSsh.java.lang +{ + /// + /// Summary description for Thread. + /// + public class Thread + { + Threading.Thread t; + + public Thread(Threading.Thread t) + { + this.t=t; + } + public Thread(ThreadStart ts):this(new Threading.Thread(ts)) + { + } + + public Thread(Runnable r):this(new ThreadStart(r.run)) + { + } + + public void setName(string name) + { + t.Name=name; + } + + public void start() + { + t.Start(); + } + + public bool isAlive() + { + return t.IsAlive; + } + + public void yield() + { + } + + public void interrupt() + { + try + { + t.Interrupt(); + }catch + { + } + } + + public void notifyAll() + { + Monitor.PulseAll(this); + } + + public static void Sleep(int t) + { + Threading.Thread.Sleep(t); + } + + public static void sleep(int t) + { + Sleep(t); + } + + public static Thread currentThread() + { + return new Thread( Threading.Thread.CurrentThread ); + } + } +} diff --git a/SharpSSH/java/net/InetAddress.cs b/SharpSSH/java/net/InetAddress.cs new file mode 100644 index 00000000..cb28dfff --- /dev/null +++ b/SharpSSH/java/net/InetAddress.cs @@ -0,0 +1,61 @@ +using System; +using System.Net; + +namespace Tamir.SharpSsh.java.net +{ + /// + /// Summary description for InetAddress. + /// + public class InetAddress + { + internal IPAddress addr; + public InetAddress(string addr) + { + this.addr = IPAddress.Parse(addr); + } + public InetAddress(IPAddress addr) + { + this.addr = addr; + } + + public bool isAnyLocalAddress() + { + return IPAddress.IsLoopback(addr); + } + + public bool equals(InetAddress addr) + { + return addr.ToString().Equals( addr.ToString()); + } + + public bool equals(string addr) + { + return addr.ToString().Equals( addr.ToString()); + } + + public override string ToString() + { + return addr.ToString (); + } + + public override bool Equals(object obj) + { + return equals (obj.ToString()); + } + + public string getHostAddress() + { + return ToString(); + } + + public override int GetHashCode() + { + return base.GetHashCode (); + } + + public static InetAddress getByName(string name) + { + return new InetAddress( Dns.GetHostByName(name).AddressList[0] ); + } + } +} diff --git a/SharpSSH/java/net/ServerSocket.cs b/SharpSSH/java/net/ServerSocket.cs new file mode 100644 index 00000000..52daa8a0 --- /dev/null +++ b/SharpSSH/java/net/ServerSocket.cs @@ -0,0 +1,27 @@ +using System; +using System.Net; +using System.Net.Sockets; + +namespace Tamir.SharpSsh.java.net +{ + /// + /// Summary description for ServerSocket. + /// + public class ServerSocket : TcpListener + { + public ServerSocket(int port, int arg, InetAddress addr) : base(addr.addr, port) + { + this.Start(); + } + + public Tamir.SharpSsh.java.net.Socket accept() + { + return new Tamir.SharpSsh.java.net.Socket( this.AcceptSocket() ); + } + + public void close() + { + this.Stop(); + } + } +} diff --git a/SharpSSH/java/net/Socket.cs b/SharpSSH/java/net/Socket.cs new file mode 100644 index 00000000..5b28ea6e --- /dev/null +++ b/SharpSSH/java/net/Socket.cs @@ -0,0 +1,95 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Sockets; +using Net = System.Net; +using Sock = System.Net.Sockets.Socket; + +namespace Tamir.SharpSsh.java.net +{ + /// + /// Summary description for Socket. + /// + public class Socket + { + internal Sock sock; + + protected void SetSocketOption(SocketOptionLevel level, SocketOptionName name, int val) + { + try + { + sock.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, val); + } + catch + { + } + } + +// public Socket(AddressFamily af, SocketType st, ProtocolType pt) +// { +// this.sock = new Sock(af, st, pt); +// this.sock.Connect(); +// } + + public Socket(string host, int port) + { + IPEndPoint ep = new IPEndPoint(Dns.GetHostByName(host).AddressList[0], port); + this.sock = new Sock(ep.AddressFamily, + SocketType.Stream, ProtocolType.Tcp); + this.sock.Connect(ep); + } + + public Socket(Sock sock) + { + this.sock = sock; + } + + public Stream getInputStream() + { + return new Net.Sockets.NetworkStream(sock); + } + + public Stream getOutputStream() + { + return new Net.Sockets.NetworkStream(sock); + } + + public bool isConnected() + { + return sock.Connected; + } + + public void setTcpNoDelay(bool b) + { + if(b) + { + SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, 1); + } + else + { + SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, 0); + } + } + + public void setSoTimeout(int t) + { + SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, t); + SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, t); + } + + public void close() + { + sock.Close(); + } + + public InetAddress getInetAddress() + { + return new InetAddress( ((IPEndPoint) sock.RemoteEndPoint).Address ); + } + + public int getPort() + { + return ((IPEndPoint) sock.RemoteEndPoint).Port; + } + } +} diff --git a/SharpSSH/java/util/Arrays.cs b/SharpSSH/java/util/Arrays.cs new file mode 100644 index 00000000..4a9fbe78 --- /dev/null +++ b/SharpSSH/java/util/Arrays.cs @@ -0,0 +1,19 @@ +using System; + +namespace Tamir.SharpSsh.java.util +{ + /// + /// Summary description for Arrays. + /// + public class Arrays + { + internal static bool equals(byte[] foo, byte[] bar) + { + int i=foo.Length; + if(i!=bar.Length) return false; + for(int j=0; j + /// Summary description for Enumeration. + /// + public class Enumeration + { + private IEnumerator e; + private bool hasMore; + public Enumeration(IEnumerator e) + { + this.e=e; + hasMore = e.MoveNext(); + } + + public bool hasMoreElements() + { + return hasMore; + } + + public object nextElement() + { + object o = e.Current; + hasMore = e.MoveNext(); + return o; + } + } +} diff --git a/SharpSSH/java/util/Hashtable.cs b/SharpSSH/java/util/Hashtable.cs new file mode 100644 index 00000000..e8a77d14 --- /dev/null +++ b/SharpSSH/java/util/Hashtable.cs @@ -0,0 +1,43 @@ +using System; +using Collections = System.Collections; + +namespace Tamir.SharpSsh.java.util +{ + /// + /// Summary description for Hashtable. + /// + public class Hashtable + { + internal Collections.Hashtable h; + + public Hashtable() + { + h= new Collections.Hashtable(); + } + public Hashtable(Collections.Hashtable h) + { + this.h=h; + } + + public void put(object key, object item) + { + h.Add(key, item); + } + + public object get(object key) + { + return h[key]; + } + + public Enumeration keys() + { + return new Enumeration( h.Keys.GetEnumerator() ); + } + + public object this[object key] + { + get{return get(key);} + set{h[key]=value;} + } + } +} diff --git a/SharpSSH/java/util/JavaString.cs b/SharpSSH/java/util/JavaString.cs new file mode 100644 index 00000000..b9fac6b5 --- /dev/null +++ b/SharpSSH/java/util/JavaString.cs @@ -0,0 +1,27 @@ +using System; + +namespace Tamir.SharpSsh.java.util +{ + /// + /// Summary description for JavaString. + /// + public class JavaString : Tamir.SharpSsh.java.String + { + public JavaString(string s) : base(s) + { + } + + public JavaString(object o):base(o) + { + } + + public JavaString(byte[] arr):base(arr) + { + } + + public JavaString(byte[] arr, int offset, int len):base(arr, offset, len) + { + } + + } +} diff --git a/SharpSSH/java/util/Vector.cs b/SharpSSH/java/util/Vector.cs new file mode 100644 index 00000000..1a6b270d --- /dev/null +++ b/SharpSSH/java/util/Vector.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections; + +namespace Tamir.SharpSsh.java.util +{ + /// + /// Summary description for Vector. + /// + public class Vector : ArrayList + { + public int size() + { + return this.Count; + } + + public void addElement(object o) + { + this.Add(o); + } + + public void add(object o) + { + addElement(o); + } + + public void removeElement(object o) + { + this.Remove(o); + } + + public bool remove(object o) + { + this.Remove(o); + return true; + } + + public object elementAt(int i) + { + return this[i]; + } + + public object get(int i) + { + return elementAt(i);; + } + + public void clear() + { + this.Clear(); + } + + public string toString() + { + return ToString(); + } + } +} diff --git a/SharpSSH/jsch/Buffer.cs b/SharpSSH/jsch/Buffer.cs new file mode 100644 index 00000000..985b0c22 --- /dev/null +++ b/SharpSSH/jsch/Buffer.cs @@ -0,0 +1,262 @@ +using System; + +namespace Tamir.SharpSsh.jsch +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + public class Buffer + { + static byte[] tmp=new byte[4]; + internal byte[] buffer; + internal int index; + internal int s; + public Buffer(int size) + { + buffer=new byte[size]; + index=0; + s=0; + } + public Buffer(byte[] buffer) + { + this.buffer=buffer; + index=0; + s=0; + } + public Buffer():this(1024*10*2){ } + public void putByte(byte foo) + { + buffer[index++]=foo; + } + public void putByte(byte[] foo) + { + putByte(foo, 0, foo.Length); + } + public void putByte(byte[] foo, int begin, int length) + { + Array.Copy(foo, begin, buffer, index, length); + index+=length; + } + public void putString(byte[] foo) + { + putString(foo, 0, foo.Length); + } + public void putString(byte[] foo, int begin, int length) + { + putInt(length); + putByte(foo, begin, length); + } + public void putInt(int v) + { + uint val = (uint)v; + tmp[0]=(byte)(val >> 24); + tmp[1]=(byte)(val >> 16); + tmp[2]=(byte)(val >> 8); + tmp[3]=(byte)(val); + Array.Copy(tmp, 0, buffer, index, 4); + index+=4; + } + public void putLong(long v) + { + ulong val = (ulong)v; + tmp[0]=(byte)(val >> 56); + tmp[1]=(byte)(val >> 48); + tmp[2]=(byte)(val >>40); + tmp[3]=(byte)(val >> 32); + Array.Copy(tmp, 0, buffer, index, 4); + tmp[0]=(byte)(val >> 24); + tmp[1]=(byte)(val >> 16); + tmp[2]=(byte)(val >> 8); + tmp[3]=(byte)(val); + Array.Copy(tmp, 0, buffer, index+4, 4); + index+=8; + } + internal void skip(int n) + { + index+=n; + } + internal void putPad(int n) + { + while(n>0) + { + buffer[index++]=(byte)0; + n--; + } + } + public void putMPInt(byte[] foo) + { + int i=foo.Length; + if((foo[0]&0x80)!=0) + { + i++; + putInt(i); + putByte((byte)0); + } + else + { + putInt(i); + } + putByte(foo); + } + public int getLength() + { + return index-s; + } + public int getOffSet() + { + return s; + } + public void setOffSet(int s) + { + this.s=s; + } + public long getLong() + { + long foo = getInt()&0xffffffffL; + foo = ((foo<<32)) | (getInt()&0xffffffffL); + return foo; + } + public int getInt() + { + uint foo = (uint) getShort(); + foo = ((foo<<16)&0xffff0000) | ((uint)getShort()&0xffff); + return (int)foo; + } + internal int getShort() + { + int foo = getByte(); + foo = ((foo<<8)&0xff00)|(getByte()&0xff); + return foo; + } + public int getByte() + { + return (buffer[s++]&0xff); + } + public void getByte(byte[] foo) + { + getByte(foo, 0, foo.Length); + } + void getByte(byte[] foo, int start, int len) + { + Array.Copy(buffer, s, foo, start, len); + s+=len; + } + public int getByte(int len) + { + int foo=s; + s+=len; + return foo; + } + public byte[] getMPInt() + { + int i=getInt(); + byte[] foo=new byte[i]; + getByte(foo, 0, i); + return foo; + } + public byte[] getMPIntBits() + { + int bits=getInt(); + int bytes=(bits+7)/8; + byte[] foo=new byte[bytes]; + getByte(foo, 0, bytes); + if((foo[0]&0x80)!=0) + { + byte[] bar=new byte[foo.Length+1]; + bar[0]=0; // ?? + Array.Copy(foo, 0, bar, 1, foo.Length); + foo=bar; + } + return foo; + } + public byte[] getString() + { + int i=getInt(); + byte[] foo=new byte[i]; + getByte(foo, 0, i); + return foo; + } + internal byte[] getString(int[]start, int[]len) + { + int i=getInt(); + start[0]=getByte(i); + len[0]=i; + return buffer; + } + public void reset() + { + index=0; + s=0; + } + public void shift() + { + if(s==0)return; + Array.Copy(buffer, s, buffer, 0, index-s); + index=index-s; + s=0; + } + internal void rewind() + { + s=0; + } + + /* + static String[] chars={ + "0","1","2","3","4","5","6","7","8","9", "a","b","c","d","e","f" + }; + static void dump_buffer(){ + int foo; + for(int i=0; i>>4)&0xf]); + System.out.print(chars[foo&0xf]); + if(i%16==15){ + System.out.println(""); + continue; + } + if(i>0 && i%2==1){ + System.out.print(" "); + } + } + System.out.println(""); + } + static void dump(byte[] b){ + dump(b, 0, b.length); + } + static void dump(byte[] b, int s, int l){ + for(int i=s; i0) + { + try{Thread.sleep(50);} + catch(Exception ee){} + retry--; + } + if(!session.isConnected()) + { + throw new JSchException("session is down"); + } + if(retry==0) + { + throw new JSchException("channel is not opened."); + } + connected=true; + start(); + } + catch(Exception e) + { + connected=false; + if(e is JSchException) throw (JSchException)e; + } + } + + public virtual void setXForwarding(bool foo) + { + } + + public virtual void start(){} + + public bool isEOF() {return _eof_remote;} + + internal virtual void getData(Buffer buf) + { + setRecipient(buf.getInt()); + setRemoteWindowSize(buf.getInt()); + setRemotePacketSize(buf.getInt()); + } + + public virtual void setInputStream(Stream In) + { + io.setInputStream(In, false); + } + public virtual void setInputStream(Stream In, bool dontclose) + { + io.setInputStream(In, dontclose); + } + public virtual void setOutputStream(Stream Out) + { + io.setOutputStream(Out, false); + } + public virtual void setOutputStream(Stream Out, bool dontclose) + { + io.setOutputStream(Out, dontclose); + } + public virtual void setExtOutputStream(Stream Out) + { + io.setExtOutputStream(Out, false); + } + public virtual void setExtOutputStream(Stream Out, bool dontclose) + { + io.setExtOutputStream(Out, dontclose); + } + public virtual java.io.InputStream getInputStream() + { + PipedInputStream In= + new MyPipedInputStream( + 32*1024 // this value should be customizable. + ); + io.setOutputStream(new PassiveOutputStream(In), false); + return In; + } + public virtual java.io.InputStream getExtInputStream() + { + PipedInputStream In= + new MyPipedInputStream( + 32*1024 // this value should be customizable. + ); + io.setExtOutputStream(new PassiveOutputStream(In), false); + return In; + } + public virtual Stream getOutputStream() + { + PipedOutputStream Out=new PipedOutputStream(); + io.setInputStream(new PassiveInputStream(Out + , 32*1024 + ), false); + // io.setInputStream(new PassiveInputStream(Out), false); + return Out; + } + internal class MyPipedInputStream : PipedInputStream + { + internal MyPipedInputStream():base() { ; } + internal MyPipedInputStream(int size) :base() + { + buffer=new byte[size]; + } + internal MyPipedInputStream(PipedOutputStream Out):base(Out) { } + internal MyPipedInputStream(PipedOutputStream Out, int size):base(Out) + { + buffer=new byte[size]; + } + } + internal virtual void setLocalWindowSizeMax(int foo){ this.lwsize_max=foo; } + internal virtual void setLocalWindowSize(int foo){ this.lwsize=foo; } + internal virtual void setLocalPacketSize(int foo){ this.lmpsize=foo; } + [System.Runtime.CompilerServices.MethodImpl(MethodImplOptions.Synchronized)] + internal virtual void setRemoteWindowSize(int foo){ this.rwsize=foo; } + [System.Runtime.CompilerServices.MethodImpl(MethodImplOptions.Synchronized)] + internal virtual void addRemoteWindowSize(int foo){ this.rwsize+=foo; } + internal virtual void setRemotePacketSize(int foo){ this.rmpsize=foo; } + + public virtual void run() + { + } + + internal virtual void write(byte[] foo) + { + write(foo, 0, foo.Length); + } + internal virtual void write(byte[] foo, int s, int l) + { + try + { + // if(io.outs!=null) + io.put(foo, s, l); + } + catch(NullReferenceException e){} + } + internal virtual void write_ext(byte[] foo, int s, int l) + { + try + { + // if(io.out_ext!=null) + io.put_ext(foo, s, l); + } + catch(NullReferenceException e){} + } + + internal virtual void eof_remote() + { + _eof_remote=true; + try + { + if(io.outs!=null) + { + io.outs.Close(); + io.outs=null; + } + } + catch(NullReferenceException e){} + catch(IOException e){} + } + + internal virtual void eof() + { + //System.Out.println("EOF!!!! "+this); + //Thread.dumpStack(); + if(_close)return; + if(eof_local)return; + eof_local=true; + //close=eof; + try + { + Buffer buf=new Buffer(100); + Packet packet=new Packet(buf); + packet.reset(); + buf.putByte((byte)Session.SSH_MSG_CHANNEL_EOF); + buf.putInt(getRecipient()); + session.write(packet); + } + catch(Exception e) + { + //System.Out.println("Channel.eof"); + //e.printStackTrace(); + } + /* + if(!isConnected()){ disconnect(); } + */ + } + + /* + http://www1.ietf.org/internet-drafts/draft-ietf-secsh-connect-24.txt + + 5.3 Closing a Channel + When a party will no longer send more data to a channel, it SHOULD + send SSH_MSG_CHANNEL_EOF. + + byte SSH_MSG_CHANNEL_EOF + uint32 recipient_channel + + No explicit response is sent to this message. However, the + application may send EOF to whatever is at the other end of the + channel. Note that the channel remains open after this message, and + more data may still be sent In the other direction. This message + does not consume window space and can be sent even if no window space + is available. + + When either party wishes to terminate the channel, it sends + SSH_MSG_CHANNEL_CLOSE. Upon receiving this message, a party MUST + send back a SSH_MSG_CHANNEL_CLOSE unless it has already sent this + message for the channel. The channel is considered closed for a + party when it has both sent and received SSH_MSG_CHANNEL_CLOSE, and + the party may then reuse the channel number. A party MAY send + SSH_MSG_CHANNEL_CLOSE without having sent or received + SSH_MSG_CHANNEL_EOF. + + byte SSH_MSG_CHANNEL_CLOSE + uint32 recipient_channel + + This message does not consume window space and can be sent even if no + window space is available. + + It is recommended that any data sent before this message is delivered + to the actual destination, if possible. + */ + + internal virtual void close() + { + //System.Out.println("close!!!!"); + if(_close)return; + _close=true; + try + { + Buffer buf=new Buffer(100); + Packet packet=new Packet(buf); + packet.reset(); + buf.putByte((byte)Session.SSH_MSG_CHANNEL_CLOSE); + buf.putInt(getRecipient()); + session.write(packet); + } + catch(Exception e) + { + //e.printStackTrace(); + } + } + public virtual bool isClosed() + { + return _close; + } + internal static void disconnect(Session session) + { + Channel[] channels=null; + int count=0; + lock(pool) + { + channels=new Channel[pool.size()]; + for(int i=0; i0) +// { +// try{System.Threading.Thread.Sleep(50);} +// catch{} +// retry--; +// } +// if(!session.IsConnected()) +// { +// throw new JSchException("session is down"); +// } +// if(retry==0) +// { +// throw new JSchException("channel is not opened."); +// } +// start(); +// } +// catch(Exception e) +// { +// if(e is JSchException) throw (JSchException)e; +// } +// } +// +// public virtual void setXForwarding(bool foo) +// { +// } +// +// public virtual void start() {} +// +// public bool isEOF() {return eof_remote;} +// +// internal virtual void getData(Buffer buf) +// { +// setRecipient(buf.getInt()); +// setRemoteWindowSize(buf.getInt()); +// setRemotePacketSize(buf.getInt()); +// } +// +// public RequestWindowChange getRequestWindowChange() +// { +// return new RequestWindowChange(); +// } +// +// public virtual void setInputStream(Stream ins) +// { +// io.setInputStream(ins); +// } +// public virtual void setOutputStream(Stream outs) +// { +// io.setOutputStream(outs); +// } +// public virtual void setExtOutputStream(Stream outs) +// { +// io.setExtOutputStream(outs); +// } +// public virtual Stream getInputStream() +// { +// PipedInputStream ins= +// new MyPipedInputStream( +// 32*1024 // this value should be customizable. +// ); +// io.setOutputStream(new PassiveOutputStream(ins)); +// return ins; +// } +// public virtual Stream getExtInputStream() +// { +// PipedInputStream ins= +// new MyPipedInputStream( +// 32*1024 // this value should be customizable. +// ); +// io.setExtOutputStream(new PassiveOutputStream(ins)); +// return ins; +// } +// public virtual Stream getOutputStream() +// { +// PipedOutputStream outs=new PipedOutputStream(); +// io.setInputStream(new PassiveInputStream(outs)); +// return outs; +// } +// +// internal void setLocalWindowSizeMax(int foo){ this.lwsize_max=foo; } +// internal void setLocalWindowSize(int foo){ this.lwsize=foo; } +// internal void setLocalPacketSize(int foo){ this.lmpsize=foo; } +// internal void setRemoteWindowSize(int foo){ this.rwsize=foo; } +// internal void addRemoteWindowSize(int foo){ this.rwsize+=foo; } +// internal void setRemotePacketSize(int foo){ this.rmpsize=foo; } +// +// public virtual void run() +// { +// } +// +// internal virtual void write(byte[] foo) +// { +// write(foo, 0, foo.Length); +// } +// internal virtual void write(byte[] foo, int s, int l) +// { +// //if(eof_remote)return; +// if(io.outs!=null) +// io.put(foo, s, l); +// } +// internal void write_ext(byte[] foo, int s, int l) +// { +// //if(eof_remote)return; +// if(io.out_ext!=null) +// io.put_ext(foo, s, l); +// } +// +// internal void eof() +// { +// //System.out.println("EOF!!!! "+this); +// //Thread.dumpStack(); +// if(eof_local)return; +// eof_local=true; +// //close=eof; +// try +// { +// Buffer buf=new Buffer(100); +// Packet packet=new Packet(buf); +// packet.reset(); +// buf.putByte((byte)Session.SSH_MSG_CHANNEL_EOF); +// buf.putInt(getRecipient()); +// session.write(packet); +// } +// catch +// { +// //System.out.println("Channel.eof"); +// //e.printStackTrace(); +// } +// if(!isConnected()) +// { +// disconnect(); +// } +// } +// +// internal void close() +// { +// //System.out.println("close!!!!"); +// if(_close)return; +// _close=true; +// try +// { +// Buffer buf=new Buffer(100); +// Packet packet=new Packet(buf); +// packet.reset(); +// buf.putByte((byte)Session.SSH_MSG_CHANNEL_CLOSE); +// buf.putInt(getRecipient()); +// session.write(packet); +// session.disconnect(); +// } +// catch +// { +// //e.printStackTrace(); +// } +// } +// internal static void eof(Session session) +// { +// Channel[] channels=null; +// int count=0; +// lock(pool) +// { +// channels=new Channel[pool.Count]; +// for(int i=0; i0 && + !_eof_remote) + { + //Thread.sleep(500); + Thread.Sleep(50); + retry--; + } + } + catch + { + } + + if(!session.isConnected()) + { + throw new JSchException("session is down"); + } + if(retry==0 || this._eof_remote) + { + throw new JSchException("channel is not opened."); + } + /* + if(this.eof_remote){ // failed to open + disconnect(); + return; + } + */ + + connected=true; + + thread=new Thread(this); + thread.start(); + } + catch(Exception e) + { + io.close(); + io=null; + Channel.del(this); + if (e is JSchException) + { + throw (JSchException) e; + } + } + } + + public override void run() + { + // thread=Thread.currentThread(); + //System.out.println("rmpsize: "+rmpsize+", lmpsize: "+lmpsize); + Buffer buf=new Buffer(rmpsize); + // Buffer buf=new Buffer(lmpsize); + Packet packet=new Packet(buf); + int i=0; + try + { + while(isConnected() && + thread!=null && + io!=null && + io.ins!=null) + { + i=io.ins.Read(buf.buffer, + 14, + buf.buffer.Length-14 + -32 -20 // padding and mac + ); + if(i<=0) + { + eof(); + break; + } + if(_close)break; + packet.reset(); + buf.putByte((byte)Session.SSH_MSG_CHANNEL_DATA); + buf.putInt(recipient); + buf.putInt(i); + buf.skip(i); + session.write(packet, this, i); + } + } + catch + { + } + disconnect(); + //System.out.println("connect end"); + + /* + try{ + packet.reset(); + buf.putByte((byte)Session.SSH_MSG_CHANNEL_EOF); + buf.putInt(recipient); + session.write(packet); + } + catch(Exception e){ + } + */ + // close(); + } + + public override void setInputStream(Stream ins) + { + io.setInputStream(ins); + } + public override void setOutputStream(Stream outs) + { + io.setOutputStream(outs); + } + + public void setHost(String host){this.host=host;} + public void setPort(int port){this.port=port;} + public void setOrgIPAddress(String foo){this.originator_IP_address=foo;} + public void setOrgPort(int foo){this.originator_port=foo;} + } + +} diff --git a/SharpSSH/jsch/ChannelExec.cs b/SharpSSH/jsch/ChannelExec.cs new file mode 100644 index 00000000..b6064e81 --- /dev/null +++ b/SharpSSH/jsch/ChannelExec.cs @@ -0,0 +1,98 @@ +using System; +using System.Net; +using System.Net.Sockets; +using System.IO; +using Tamir.SharpSsh.java.lang; + +namespace Tamir.SharpSsh.jsch +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use ins source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions ins binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer ins + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + public class ChannelExec : ChannelSession + { + bool xforwading=false; + bool pty=false; + String command=""; + /* + ChannelExec(){ + super(); + type="session".getBytes(); + io=new IO(); + } + */ + public override void setXForwarding(bool foo){ xforwading=foo; } + public void setPty(bool foo){ pty=foo; } + public override void start() + { + try + { + Request request; + + if(xforwading) + { + request=new RequestX11(); + request.request(session, this); + } + + if(pty) + { + request=new RequestPtyReq(); + request.request(session, this); + } + + request=new RequestExec(command); + request.request(session, this); + } + catch(Exception e) + { + throw new JSchException("ChannelExec"); + } + thread=new Thread(this); + thread.setName("Exec thread "+session.getHost()); + thread.start(); + } + public void setCommand(String foo){ command=foo;} + public override void init() + { + io.setInputStream(session.In); + io.setOutputStream(session.Out); + } + //public void finalize() throws java.lang.Throwable{ super.finalize(); } + public void setErrStream(Stream Out) + { + setExtOutputStream(Out); + } + public Stream getErrStream() + { + return getExtInputStream(); + } + } + +} diff --git a/SharpSSH/jsch/ChannelForwardedTCPIP.cs b/SharpSSH/jsch/ChannelForwardedTCPIP.cs new file mode 100644 index 00000000..aefc28fa --- /dev/null +++ b/SharpSSH/jsch/ChannelForwardedTCPIP.cs @@ -0,0 +1,310 @@ +using System; +using System.IO; +using Tamir.SharpSsh.java.util; +using Tamir.SharpSsh.java.net; +using Tamir.SharpSsh.java.lang; +using Str = Tamir.SharpSsh.java.String; + +namespace Tamir.SharpSsh.jsch +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + public class ChannelForwardedTCPIP : Channel + { + + internal static java.util.Vector pool=new java.util.Vector(); + + // static private final int LOCAL_WINDOW_SIZE_MAX=0x20000; + static private int LOCAL_WINDOW_SIZE_MAX=0x100000; + static private int LOCAL_MAXIMUM_PACKET_SIZE=0x4000; + + internal SocketFactory factory=null; + internal String target; + internal int lport; + internal int rport; + + internal ChannelForwardedTCPIP() : base() + { + setLocalWindowSizeMax(LOCAL_WINDOW_SIZE_MAX); + setLocalWindowSize(LOCAL_WINDOW_SIZE_MAX); + setLocalPacketSize(LOCAL_MAXIMUM_PACKET_SIZE); + } + + public override void init () + { + try + { + io=new IO(); + if(lport==-1) + { + Class c=Class.forName(target); + ForwardedTCPIPDaemon daemon=(ForwardedTCPIPDaemon)c.newInstance(); + daemon.setChannel(this); + Object[] foo=getPort(session, rport); + daemon.setArg((Object[])foo[3]); + new Thread(daemon).start(); + connected=true; + return; + } + else + { + Socket socket=(factory==null) ? + new Socket(target, lport) : + factory.createSocket(target, lport); + socket.setTcpNoDelay(true); + io.setInputStream(socket.getInputStream()); + io.setOutputStream(socket.getOutputStream()); + connected=true; + } + } + catch(Exception e) + { + Console.WriteLine("target={0},port={1}",target,lport); + Console.WriteLine(e); + } + } + + public override void run() + { + thread=Thread.currentThread(); + Buffer buf=new Buffer(rmpsize); + Packet packet=new Packet(buf); + int i=0; + try + { + while(thread!=null && io!=null && io.ins!=null) + { + i=io.ins.Read(buf.buffer, + 14, + buf.buffer.Length-14 + -32 -20 // padding and mac + ); + if(i<=0) + { + eof(); + break; + } + packet.reset(); + if(_close)break; + buf.putByte((byte)Session.SSH_MSG_CHANNEL_DATA); + buf.putInt(recipient); + buf.putInt(i); + buf.skip(i); + session.write(packet, this, i); + } + } + catch(Exception e) + { + //System.out.println(e); + } + + //thread=null; + //eof(); + disconnect(); + } + internal override void getData(Buffer buf) + { + setRecipient(buf.getInt()); + setRemoteWindowSize(buf.getInt()); + setRemotePacketSize(buf.getInt()); + byte[] addr=buf.getString(); + int port=buf.getInt(); + byte[] orgaddr=buf.getString(); + int orgport=buf.getInt(); + + /* + System.out.println("addr: "+new String(addr)); + System.out.println("port: "+port); + System.out.println("orgaddr: "+new String(orgaddr)); + System.out.println("orgport: "+orgport); + */ + + lock(pool) + { + for(int i=0; i=5) + { + this.factory=((SocketFactory)foo[4]); + } + break; + } + if(target==null) + { + Console.WriteLine("??"); + } + } + } + + internal static Object[] getPort(Session session, int rport) + { + lock(pool) + { + for(int i=0; i"); + /* + if(thread!=null){ return; } + thread=Thread.currentThread(); + */ + + // Buffer buf=new Buffer(); + Buffer buf=new Buffer(rmpsize); + Packet packet=new Packet(buf); + int i=-1; + try + { + while(isConnected() && + thread!=null && + io!=null && + io.ins!=null) + { + i=io.ins.Read(buf.buffer, + 14, + buf.buffer.Length-14 + -32 -20 // padding and mac + ); + if(i==0)continue; + if(i==-1) + { + eof(); + break; + } + if(_close)break; + packet.reset(); + buf.putByte((byte)Session.SSH_MSG_CHANNEL_DATA); + buf.putInt(recipient); + buf.putInt(i); + buf.skip(i); + session.write(packet, this, i); + } + } + catch(Exception e) + { + Console.WriteLine("# ChannelSession.run"); + Console.WriteLine(e); + } + if(thread!=null) + { + //lock(thread){ System.Threading.Monitor.PulseAll(this);/*thread.notifyAll();*/ } + } + thread=null; + //System.out.println(this+":run <"); + } + } +} diff --git a/SharpSSH/jsch/ChannelSftp.cs b/SharpSSH/jsch/ChannelSftp.cs new file mode 100644 index 00000000..869e5e48 --- /dev/null +++ b/SharpSSH/jsch/ChannelSftp.cs @@ -0,0 +1,2651 @@ +//using System; +using System.Runtime.CompilerServices; +using Tamir.Streams; +using Tamir.SharpSsh.java.io; +using Tamir.SharpSsh.java.lang; +using Tamir.SharpSsh.java.util; +using Tamir.SharpSsh.java; + +namespace Tamir.SharpSsh.jsch +{ + /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ + /* + Copyright (c) 2002,2003,2004,2005,2006 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + /// + /// Based on JSch-0.1.30 + /// + public class ChannelSftp : ChannelSession + { + private static byte SSH_FXP_INIT= 1; + private static byte SSH_FXP_VERSION= 2; + private static byte SSH_FXP_OPEN= 3; + private static byte SSH_FXP_CLOSE= 4; + private static byte SSH_FXP_READ= 5; + private static byte SSH_FXP_WRITE= 6; + private static byte SSH_FXP_LSTAT= 7; + private static byte SSH_FXP_FSTAT= 8; + private static byte SSH_FXP_SETSTAT= 9; + private static byte SSH_FXP_FSETSTAT= 10; + private static byte SSH_FXP_OPENDIR= 11; + private static byte SSH_FXP_READDIR= 12; + private static byte SSH_FXP_REMOVE= 13; + private static byte SSH_FXP_MKDIR= 14; + private static byte SSH_FXP_RMDIR= 15; + private static byte SSH_FXP_REALPATH= 16; + private static byte SSH_FXP_STAT= 17; + private static byte SSH_FXP_RENAME= 18; + private static byte SSH_FXP_READLINK= 19; + private static byte SSH_FXP_SYMLINK= 20; + private static byte SSH_FXP_STATUS= 101; + private static byte SSH_FXP_HANDLE= 102; + private static byte SSH_FXP_DATA= 103; + private static byte SSH_FXP_NAME= 104; + private static byte SSH_FXP_ATTRS= 105; + private static byte SSH_FXP_EXTENDED= (byte)200; + private static byte SSH_FXP_EXTENDED_REPLY= (byte)201; + + // pflags + private static int SSH_FXF_READ= 0x00000001; + private static int SSH_FXF_WRITE= 0x00000002; + private static int SSH_FXF_APPEND= 0x00000004; + private static int SSH_FXF_CREAT= 0x00000008; + private static int SSH_FXF_TRUNC= 0x00000010; + private static int SSH_FXF_EXCL= 0x00000020; + + private static int SSH_FILEXFER_ATTR_SIZE= 0x00000001; + private static int SSH_FILEXFER_ATTR_UIDGID= 0x00000002; + private static int SSH_FILEXFER_ATTR_PERMISSIONS= 0x00000004; + private static int SSH_FILEXFER_ATTR_ACMODTIME= 0x00000008; + private static uint SSH_FILEXFER_ATTR_EXTENDED= 0x80000000; + + public static int SSH_FX_OK= 0; + public static int SSH_FX_EOF= 1; + public static int SSH_FX_NO_SUCH_FILE= 2; + public static int SSH_FX_PERMISSION_DENIED= 3; + public static int SSH_FX_FAILURE= 4; + public static int SSH_FX_BAD_MESSAGE= 5; + public static int SSH_FX_NO_CONNECTION= 6; + public static int SSH_FX_CONNECTION_LOST= 7; + public static int SSH_FX_OP_UNSUPPORTED= 8; + /* + SSH_FX_OK + Indicates successful completion of the operation. + SSH_FX_EOF + indicates end-of-file condition; for SSH_FX_READ it means that no + more data is available in the file, and for SSH_FX_READDIR it + indicates that no more files are contained in the directory. + SSH_FX_NO_SUCH_FILE + is returned when a reference is made to a file which should exist + but doesn't. + SSH_FX_PERMISSION_DENIED + is returned when the authenticated user does not have sufficient + permissions to perform the operation. + SSH_FX_FAILURE + is a generic catch-all error message; it should be returned if an + error occurs for which there is no more specific error code + defined. + SSH_FX_BAD_MESSAGE + may be returned if a badly formatted packet or protocol + incompatibility is detected. + SSH_FX_NO_CONNECTION + is a pseudo-error which indicates that the client has no + connection to the server (it can only be generated locally by the + client, and MUST NOT be returned by servers). + SSH_FX_CONNECTION_LOST + is a pseudo-error which indicates that the connection to the + server has been lost (it can only be generated locally by the + client, and MUST NOT be returned by servers). + SSH_FX_OP_UNSUPPORTED + indicates that an attempt was made to perform an operation which + is not supported for the server (it may be generated locally by + the client if e.g. the version number exchange indicates that a + required feature is not supported by the server, or it may be + returned by the server if the server does not implement an + operation). + */ + private static int MAX_MSG_LENGTH = 256* 1024; + + public static int OVERWRITE=0; + public static int RESUME=1; + public static int APPEND=2; + + // private bool interactive=true; + private bool interactive=false; + internal int seq=1; + private int[] ackid=new int[1]; + private Buffer buf; + private Packet packet;//=new Packet(buf); + + private String _version="3"; + private int server_version=3; + /* + 10. Changes from previous protocol versions + The SSH File Transfer Protocol has changed over time, before it's + standardization. The following is a description of the incompatible + changes between different versions. + 10.1 Changes between versions 3 and 2 + o The SSH_FXP_READLINK and SSH_FXP_SYMLINK messages were added. + o The SSH_FXP_EXTENDED and SSH_FXP_EXTENDED_REPLY messages were added. + o The SSH_FXP_STATUS message was changed to include fields `error + message' and `language tag'. + 10.2 Changes between versions 2 and 1 + o The SSH_FXP_RENAME message was added. + 10.3 Changes between versions 1 and 0 + o Implementation changes, no actual protocol changes. + */ + + private static String file_separator=java.io.File.separator; + private static char file_separatorc=java.io.File.separatorChar; + + private String cwd; + private String home; + private String lcwd; + + internal ChannelSftp(){packet=new Packet(buf);} + + public override void init() + { + /* + io.setInputStream(session.in); + io.setOutputStream(session.out); + */ + } + + public override void start() + { //throws JSchException{ + try + { + + PipedOutputStream pos=new PipedOutputStream(); + io.setOutputStream(pos); + PipedInputStream pis=new MyPipedInputStream(pos, 32*1024); + io.setInputStream(pis); + + Request request=new RequestSftp(); + request.request(session, this); + + /* + System.err.println("lmpsize: "+lmpsize); + System.err.println("lwsize: "+lwsize); + System.err.println("rmpsize: "+rmpsize); + System.err.println("rwsize: "+rwsize); + */ + + buf=new Buffer(rmpsize); + packet=new Packet(buf); + int i=0; + int length; + int type; + byte[] str; + + // send SSH_FXP_INIT + sendINIT(); + + // receive SSH_FXP_VERSION + Header _header=new Header(); + _header=header(buf, _header); + length=_header.length; + if(length > MAX_MSG_LENGTH) + { + throw new SftpException(SSH_FX_FAILURE, "Received message is too long: " + length); + } + type=_header.type; // 2 -> SSH_FXP_VERSION + server_version=_header.rid; + skip(length); + //System.err.println("SFTP protocol server-version="+server_version); + //System.Console.WriteLine("server_version="+server_version+", type="+type+", length="+length+", i="+i); + + // send SSH_FXP_REALPATH + sendREALPATH(new String(".").getBytes()); + + // receive SSH_FXP_NAME + _header=header(buf, _header); + length=_header.length; + type=_header.type; // 104 -> SSH_FXP_NAME + buf.rewind(); + fill(buf.buffer, 0, length); + i=buf.getInt(); // count + //System.Console.WriteLine("type="+type+", length="+length+", i="+i); + str=buf.getString(); // filename + //System.Console.WriteLine("str.length="+str.Length); + home=cwd=new String(str); + str=buf.getString(); // logname + // SftpATTRS.getATTR(buf); // attrs + + lcwd=new File(".").getCanonicalPath(); + } + catch(Exception e) + { + //System.out.println(e); + //System.Console.WriteLine(e); + if(e is JSchException) throw (JSchException)e; + throw new JSchException(e.toString()); + } + } + + public void quit(){ disconnect();} + public void exit(){ disconnect();} + public void lcd(String path) + { //throws SftpException{ + path=localAbsolutePath(path); + if((new File(path)).isDirectory()) + { + try + { + path=(new File(path)).getCanonicalPath(); + } + catch(Exception e){} + lcwd=path; + return; + } + throw new SftpException(SSH_FX_NO_SUCH_FILE, "No such directory"); + } + + /* + cd /tmp + c->s REALPATH + s->c NAME + c->s STAT + s->c ATTR + */ + public void cd(String path) + { + //throws SftpException{ + try + { + path=remoteAbsolutePath(path); + + Vector v=glob_remote(path); + if(v.size()!=1) + { + throw new SftpException(SSH_FX_FAILURE, v.toString()); + } + path=(String)(v.elementAt(0)); + sendREALPATH(path.getBytes()); + + Header _header=new Header(); + _header=header(buf, _header); + int length=_header.length; + int type=_header.type; + buf.rewind(); + fill(buf.buffer, 0, length); + + if(type!=101 && type!=104) + { + throw new SftpException(SSH_FX_FAILURE, ""); + } + int i; + if(type==101) + { + i=buf.getInt(); + throwStatusError(buf, i); + } + i=buf.getInt(); + byte[] str=buf.getString(); + if(str!=null && str[0]!='/') + { + str=(cwd+"/"+new String(str)).getBytes(); + } + str=buf.getString(); // logname + i=buf.getInt(); // attrs + + String newpwd=new String(str); + SftpATTRS attr=_stat(newpwd); + if((attr.getFlags()&SftpATTRS.SSH_FILEXFER_ATTR_PERMISSIONS)==0) + { + throw new SftpException(SSH_FX_FAILURE, + "Can't change directory: "+path); + } + if(!attr.isDir()) + { + throw new SftpException(SSH_FX_FAILURE, + "Can't change directory: "+path); + } + cwd=newpwd; + } + catch(Exception e) + { + if(e is SftpException) throw (SftpException)e; + throw new SftpException(SSH_FX_FAILURE, ""); + } + } + + /* + put foo + c->s OPEN + s->c HANDLE + c->s WRITE + s->c STATUS + c->s CLOSE + s->c STATUS + */ + public void put(String src, String dst) + { //throws SftpException{ + put(src, dst, null, OVERWRITE); + } + public void put(String src, String dst, int mode) + { //throws SftpException{ + put(src, dst, null, mode); + } + public void put(String src, String dst, + SftpProgressMonitor monitor) + { //throws SftpException{ + put(src, dst, monitor, OVERWRITE); + } + public void put(String src, String dst, + SftpProgressMonitor monitor, int mode) + { + //throws SftpException{ + src=localAbsolutePath(src); + dst=remoteAbsolutePath(dst); + + //System.err.println("src: "+src+", "+dst); + try + { + Vector v=glob_remote(dst); + int vsize=v.size(); + if(vsize!=1) + { + if(vsize==0) + { + if(isPattern(dst)) + throw new SftpException(SSH_FX_FAILURE, dst); + else + dst=Util.unquote(dst); + } + throw new SftpException(SSH_FX_FAILURE, v.toString()); + } + else + { + dst=(String)(v.elementAt(0)); + } + + //System.err.println("dst: "+dst); + + bool _isRemoteDir=isRemoteDir(dst); + + v=glob_local(src); + //System.err.println("glob_local: "+v+" dst="+dst); + vsize=v.size(); + + StringBuffer dstsb=null; + if(_isRemoteDir) + { + if(!dst.endsWith("/")) + { + dst+="/"; + } + dstsb=new StringBuffer(dst); + } + else if(vsize>1) + { + throw new SftpException(SSH_FX_FAILURE, "Copying multiple files, but destination is missing or a file."); + } + + for(int j=0; j0) + { + long skipped=src.skip(skip); + if(skipped0){ + // _len-=sendWRITE(handle, _offset[0], d, s, _len); + + // if((count-1)==startid || + // io.ins.available()>=1024){ + // while(io.ins.available()>0){ + // if(checkStatus(ackid)){ + // _ackid=ackid[0]; + // if(startid>_ackid || _ackid>count-1){ + // throw new SftpException(SSH_FX_FAILURE, ""); + // } + // ackcount++; + // } + // else{ + // break; + // } + // } + // } + + // } + // _offset[0]+=len; + // if(monitor!=null && !monitor.count(len)){ + // throw new IOException("canceled"); + // } + // } + // catch(IOException e){ throw e; } + // catch(Exception e){ throw new IOException(e.toString()); } + // } + // byte[] _data=new byte[1]; + // public void write(int foo) { //throws java.io.IOException{ + // _data[0]=(byte)foo; + // write(_data, 0, 1); + // } + // public void close() { //throws java.io.IOException{ + + // try{ + // int _ackcount=count-startid; + // while(_ackcount>ackcount){ + // if(!checkStatus(null)){ + // break; + // } + // ackcount++; + // } + // } + // catch(SftpException e){ + // throw new IOException(e.toString()); + // } + + // if(monitor!=null)monitor.end(); + // try{ _sendCLOSE(handle); } + // catch(IOException e){ throw e; } + // catch(Exception e){ + // throw new IOException(e.toString()); + // } + // } + //}; + return outs; + } + catch(Exception e) + { + if(e is SftpException) throw (SftpException)e; + throw new SftpException(SSH_FX_FAILURE, ""); + } + } + + /**/ + public void get(String src, String dst) + { //throws SftpException{ + get(src, dst, null, OVERWRITE); + } + public void get(String src, String dst, + SftpProgressMonitor monitor) + { //throws SftpException{ + get(src, dst, monitor, OVERWRITE); + } + public void get(String src, String dst, + SftpProgressMonitor monitor, int mode) + { + //throws SftpException{ + src=remoteAbsolutePath(src); + dst=localAbsolutePath(dst); + try + { + Vector v=glob_remote(src); + int vsize=v.size(); + if(vsize==0) + { + throw new SftpException(SSH_FX_NO_SUCH_FILE, "No such file"); + } + + File dstFile=new File(dst); + bool isDstDir=dstFile.isDirectory(); + StringBuffer dstsb=null; + if(isDstDir) + { + if(!dst.endsWith(file_separator)) + { + dst+=file_separator; + } + dstsb=new StringBuffer(dst); + } + else if(vsize>1) + { + throw new SftpException(SSH_FX_FAILURE, "Copying multiple files, but destination is missing or a file."); + } + + for(int j=0; jsize_of_src) + { + throw new SftpException(SSH_FX_FAILURE, "failed to resume for "+_dst); + } + if(size_of_dst==size_of_src) + { + return; + } + } + + if(monitor!=null) + { + monitor.init(SftpProgressMonitor.GET, _src, _dst, attr.getSize()); + if(mode==RESUME) + { + monitor.count(new File(_dst).length()); + } + } + FileOutputStream fos=null; + if(mode==OVERWRITE) + { + fos=new FileOutputStream(_dst); + } + else + { + fos=new FileOutputStream(_dst, true); // append + } + + //System.err.println("_get: "+_src+", "+_dst); + _get(_src, fos, monitor, mode, new File(_dst).length()); + fos.close(); + } + } + catch(Exception e) + { + if(e is SftpException) throw (SftpException)e; + throw new SftpException(SSH_FX_FAILURE, ""); + } + } + public void get(String src, OutputStream dst) + { //throws SftpException{ + get(src, dst, null, OVERWRITE, 0); + } + public void get(String src, OutputStream dst, + SftpProgressMonitor monitor) + { //throws SftpException{ + get(src, dst, monitor, OVERWRITE, 0); + } + public void get(String src, OutputStream dst, + SftpProgressMonitor monitor, int mode, long skip) + { + //throws SftpException{ + try + { + src=remoteAbsolutePath(src); + Vector v=glob_remote(src); + if(v.size()!=1) + { + throw new SftpException(SSH_FX_FAILURE, v.toString()); + } + src=(String)(v.elementAt(0)); + + if(monitor!=null) + { + SftpATTRS attr=_stat(src); + monitor.init(SftpProgressMonitor.GET, src, "??", attr.getSize()); + if(mode==RESUME) + { + monitor.count(skip); + } + } + _get(src, dst, monitor, mode, skip); + } + catch(Exception e) + { + if(e is SftpException) throw (SftpException)e; + throw new SftpException(SSH_FX_FAILURE, ""); + } + } + + ///tamir: updated to jcsh-0.1.30 + private void _get(String src, OutputStream dst, + SftpProgressMonitor monitor, int mode, long skip) + { //throws SftpException{ + //System.out.println("_get: "+src+", "+dst); + try + { + sendOPENR(src.getBytes()); + + + Header _header=new Header(); + _header=header(buf, _header); + int length=_header.length; + int type=_header.type; + + buf.rewind(); + + fill(buf.buffer, 0, length); + + if(type!=SSH_FXP_STATUS && type!=SSH_FXP_HANDLE) + { + //System.Console.WriteLine("Type is "+type); + throw new SftpException(SSH_FX_FAILURE, "Type is "+type); + } + + if(type==SSH_FXP_STATUS) + { + int i=buf.getInt(); + throwStatusError(buf, i); + } + + byte[] handle=buf.getString(); // filename + + long offset=0; + if(mode==RESUME) + { + offset+=skip; + } + + int request_len=0; + loop: + while(true) + { + + request_len=buf.buffer.Length-13; + if(server_version==0){ request_len=1024; } + sendREAD(handle, offset, request_len); + + _header=header(buf, _header); + length=_header.length; + type=_header.type; + + int i; + if(type==SSH_FXP_STATUS) + { + buf.rewind(); + fill(buf.buffer, 0, length); + i=buf.getInt(); + if(i==SSH_FX_EOF) + { + goto BREAK; + } + throwStatusError(buf, i); + } + + if(type!=SSH_FXP_DATA) + { + goto BREAK; + } + + buf.rewind(); + fill(buf.buffer, 0, 4); length-=4; + i=buf.getInt(); // length of data + int foo=i; + while(foo>0) + { + int bar=foo; + if(bar>buf.buffer.Length) + { + bar=buf.buffer.Length; + } + i=io.ins.read(buf.buffer, 0, bar); + if(i<0) + { + goto BREAK; + } + int data_len=i; + dst.write(buf.buffer, 0, data_len); + + offset+=data_len; + foo-=data_len; + + if(monitor!=null) + { + if(!monitor.count(data_len)) + { + while(foo>0) + { + i=io.ins.read(buf.buffer, + 0, + (buf.buffer.Lengthd.Length){ + // throw new IndexOutOfBoundsException(); + // } + // if(len==0){ return 0; } + + // if(rest_length>0){ + // int foo=rest_length; + // if(foo>len) foo=len; + // int i=io.ins.read(d, s, foo); + // if(i<0){ + // throw new IOException("error"); + // } + // rest_length-=i; + // return i; + // } + + // if(buf.buffer.Length-131024){ + // len=1024; + // } + + // try{sendREAD(handle, offset, len);} + // catch(Exception e){ throw new IOException("error"); } + + // buf.rewind(); + // int i=io.ins.read(buf.buffer, 0, 13); // 4 + 1 + 4 + 4 + // if(i!=13){ + // throw new IOException("error"); + // } + + // rest_length=buf.getInt(); + // int type=buf.getByte(); + // rest_length--; + // buf.getInt(); + // rest_length-=4; + // if(type!=SSH_FXP_STATUS && type!=SSH_FXP_DATA){ + // throw new IOException("error"); + // } + // if(type==SSH_FXP_STATUS){ + // i=buf.getInt(); + // rest_length-=4; + // io.ins.read(buf.buffer, 13, rest_length); + // rest_length=0; + // if(i==SSH_FX_EOF){ + // close(); + // return -1; + // } + // //throwStatusError(buf, i); + // throw new IOException("error"); + // } + + // i=buf.getInt(); + // rest_length-=4; + // offset+=rest_length; + // int foo=i; + // if(foo>0){ + // int bar=rest_length; + // if(bar>len){ + // bar=len; + // } + // i=io.ins.read(d, s, bar); + // if(i<0){ + // return -1; + // } + // rest_length-=i; + + // if(monitor!=null){ + // if(!monitor.count(i)){ + // return -1; + // } + // } + // return i; + // } + // return 0; // ?? + // } + // public void close() { //throws IOException{ + // if(closed)return; + // closed=true; + // /* + // while(rest_length>0){ + // int foo=rest_length; + // if(foo>buf.buffer.Length){ + // foo=buf.buffer.Length; + // } + // io.ins.read(buf.buffer, 0, foo); + // rest_length-=foo; + // } + // */ + // if(monitor!=null)monitor.end(); + // try{_sendCLOSE(handle);} + // catch(Exception e){throw new IOException("error");} + // } + //}; + return ins; + } + catch(Exception e) + { + if(e is SftpException) throw (SftpException)e; + throw new SftpException(SSH_FX_FAILURE, ""); + } + } + + public java.util.Vector ls(String path) + { //throws SftpException{ + try + { + path=remoteAbsolutePath(path); + + String dir=path; + byte[] pattern=null; + SftpATTRS attr=null; + if(isPattern(dir) || + ((attr=stat(dir))!=null && !attr.isDir())) + { + int foo=path.lastIndexOf('/'); + dir=path.substring(0, ((foo==0)?1:foo)); + pattern=path.substring(foo+1).getBytes(); + } + + sendOPENDIR(dir.getBytes()); + + Header _header=new Header(); + _header=header(buf, _header); + int length=_header.length; + int type=_header.type; + buf.rewind(); + fill(buf.buffer, 0, length); + + if(type!=SSH_FXP_STATUS && type!=SSH_FXP_HANDLE) + { + throw new SftpException(SSH_FX_FAILURE, ""); + } + if(type==SSH_FXP_STATUS) + { + int i=buf.getInt(); + throwStatusError(buf, i); + } + + byte[] handle=buf.getString(); // filename + + java.util.Vector v=new java.util.Vector(); + while(true) + { + sendREADDIR(handle); + + _header=header(buf, _header); + length=_header.length; + type=_header.type; + if(type!=SSH_FXP_STATUS && type!=SSH_FXP_NAME) + { + throw new SftpException(SSH_FX_FAILURE, ""); + } + if(type==SSH_FXP_STATUS) + { + buf.rewind(); + fill(buf.buffer, 0, length); + int i=buf.getInt(); + if(i==SSH_FX_EOF) + break; + throwStatusError(buf, i); + } + + buf.rewind(); + fill(buf.buffer, 0, 4); length-=4; + int count=buf.getInt(); + + byte[] str; + int flags; + + buf.reset(); + while(count>0) + { + if(length>0) + { + buf.shift(); + int j=(buf.buffer.Length>(buf.index+length)) ? length : (buf.buffer.Length-buf.index); + int i=fill(buf.buffer, buf.index, j); + buf.index+=i; + length-=i; + } + byte[] filename=buf.getString(); + str=buf.getString(); + String longname=new String(str); + + SftpATTRS attrs=SftpATTRS.getATTR(buf); + if(pattern==null || Util.glob(pattern, filename)) + { + v.addElement(new LsEntry(new String(filename), longname, attrs)); + } + + count--; + } + } + _sendCLOSE(handle, _header); + return v; + } + catch(Exception e) + { + if(e is SftpException) throw (SftpException)e; + throw new SftpException(SSH_FX_FAILURE, ""); + } + } + + public String readlink(String path) + { + // throws SftpException{ + try + { + path=remoteAbsolutePath(path); + Vector v=glob_remote(path); + if(v.size()!=1) + { + throw new SftpException(SSH_FX_FAILURE, v.toString()); + } + path=(String)(v.elementAt(0)); + + sendREADLINK(path.getBytes()); + + Header _header=new Header(); + _header=header(buf, _header); + int length=_header.length; + int type=_header.type; + buf.rewind(); + fill(buf.buffer, 0, length); + + if(type!=SSH_FXP_STATUS && type!=SSH_FXP_NAME) + { + throw new SftpException(SSH_FX_FAILURE, ""); + } + int i; + if(type==SSH_FXP_NAME) + { + int count=buf.getInt(); // count + byte[] filename=null; + byte[] longname=null; + for(i=0; i=2) + { + throw new SftpException(SSH_FX_FAILURE, v.toString()); + } + if(vsize==1) + { + newpath=(String)(v.elementAt(0)); + } + else + { // vsize==0 + if(isPattern(newpath)) + throw new SftpException(SSH_FX_FAILURE, newpath); + newpath=Util.unquote(newpath); + } + + sendRENAME(oldpath.getBytes(), newpath.getBytes()); + + Header _header=new Header(); + _header=header(buf, _header); + int length=_header.length; + int type=_header.type; + buf.rewind(); + fill(buf.buffer, 0, length); + + if(type!=SSH_FXP_STATUS) + { + throw new SftpException(SSH_FX_FAILURE, ""); + } + + int i=buf.getInt(); + if(i==SSH_FX_OK) return; + throwStatusError(buf, i); + } + catch(Exception e) + { + if(e is SftpException) throw (SftpException)e; + throw new SftpException(SSH_FX_FAILURE, ""); + } + } + public void rm(String path) + { + //throws SftpException{ + try + { + path=remoteAbsolutePath(path); + Vector v=glob_remote(path); + int vsize=v.size(); + Header _header=new Header(); + + for(int j=0; j0) + { + i=io.ins.read(buf, s, l); + if(i<=0) + { + throw new SftpException(SSH_FX_FAILURE, ""); + } + s+=i; + l-=i; + } + } + internal bool checkStatus(int[] ackid, Header _header ) + { //throws IOException, SftpException{ + _header=header(buf, _header); + int length=_header.length; + int type=_header.type; + if(ackid!=null) + ackid[0]=_header.rid; + buf.rewind(); + fill(buf.buffer, 0, length); + + if(type!=SSH_FXP_STATUS) + { + throw new SftpException(SSH_FX_FAILURE, ""); + } + int i=buf.getInt(); + if(i!=SSH_FX_OK) + { + throwStatusError(buf, i); + } + return true; + } + + internal bool _sendCLOSE(byte[] handle, Header header) + //throws Exception + { + sendCLOSE(handle); + return checkStatus(null, header); + } + + private void sendINIT() + { //throws Exception{ + packet.reset(); + putHEAD(SSH_FXP_INIT, 5); + buf.putInt(3); // version 3 + session.write(packet, this, 5+4); + } + + private void sendREALPATH(byte[] path) + { //throws Exception{ + sendPacketPath(SSH_FXP_REALPATH, path); + } + private void sendSTAT(byte[] path) + { //throws Exception{ + sendPacketPath(SSH_FXP_STAT, path); + } + private void sendLSTAT(byte[] path) + { //throws Exception{ + sendPacketPath(SSH_FXP_LSTAT, path); + } + private void sendFSTAT(byte[] handle) + { //throws Exception{ + sendPacketPath(SSH_FXP_FSTAT, handle); + } + private void sendSETSTAT(byte[] path, SftpATTRS attr) + { //throws Exception{ + packet.reset(); + putHEAD(SSH_FXP_SETSTAT, 9+path.Length+attr.Length()); + buf.putInt(seq++); + buf.putString(path); // path + attr.dump(buf); + session.write(packet, this, 9+path.Length+attr.Length()+4); + } + private void sendREMOVE(byte[] path) + { //throws Exception{ + sendPacketPath(SSH_FXP_REMOVE, path); + } + private void sendMKDIR(byte[] path, SftpATTRS attr) + { //throws Exception{ + packet.reset(); + putHEAD(SSH_FXP_MKDIR, 9+path.Length+(attr!=null?attr.Length():4)); + buf.putInt(seq++); + buf.putString(path); // path + if(attr!=null) attr.dump(buf); + else buf.putInt(0); + session.write(packet, this, 9+path.Length+(attr!=null?attr.Length():4)+4); + } + private void sendRMDIR(byte[] path) + { //throws Exception{ + sendPacketPath(SSH_FXP_RMDIR, path); + } + private void sendSYMLINK(byte[] p1, byte[] p2) + { //throws Exception{ + sendPacketPath(SSH_FXP_SYMLINK, p1, p2); + } + private void sendREADLINK(byte[] path) + { //throws Exception{ + sendPacketPath(SSH_FXP_READLINK, path); + } + private void sendOPENDIR(byte[] path) + { //throws Exception{ + sendPacketPath(SSH_FXP_OPENDIR, path); + } + private void sendREADDIR(byte[] path) + { //throws Exception{ + sendPacketPath(SSH_FXP_READDIR, path); + } + private void sendRENAME(byte[] p1, byte[] p2) + { //throws Exception{ + sendPacketPath(SSH_FXP_RENAME, p1, p2); + } + private void sendCLOSE(byte[] path) + { //throws Exception{ + sendPacketPath(SSH_FXP_CLOSE, path); + } + private void sendOPENR(byte[] path) + { //throws Exception{ + sendOPEN(path, SSH_FXF_READ); + } + private void sendOPENW(byte[] path) + { //throws Exception{ + sendOPEN(path, SSH_FXF_WRITE|SSH_FXF_CREAT|SSH_FXF_TRUNC); + } + private void sendOPENA(byte[] path) + { //throws Exception{ + sendOPEN(path, SSH_FXF_WRITE|/*SSH_FXF_APPEND|*/SSH_FXF_CREAT); + } + private void sendOPEN(byte[] path, int mode) + { //throws Exception{ + packet.reset(); + putHEAD(SSH_FXP_OPEN, 17+path.Length); + buf.putInt(seq++); + buf.putString(path); + buf.putInt(mode); + buf.putInt(0); // attrs + session.write(packet, this, 17+path.Length+4); + } + private void sendPacketPath(byte fxp, byte[] path) + { //throws Exception{ + packet.reset(); + putHEAD(fxp, 9+path.Length); + buf.putInt(seq++); + buf.putString(path); // path + session.write(packet, this, 9+path.Length+4); + } + private void sendPacketPath(byte fxp, byte[] p1, byte[] p2) + { //throws Exception{ + packet.reset(); + putHEAD(fxp, 13+p1.Length+p2.Length); + buf.putInt(seq++); + buf.putString(p1); + buf.putString(p2); + session.write(packet, this, 13+p1.Length+p2.Length+4); + } + + internal int sendWRITE(byte[] handle, long offset, + byte[] data, int start, int length) + { //throws Exception{ + int _length=length; + packet.reset(); + if(buf.buffer.Length=0){if(path[i]=='/')break;i--;} + if(i<0){ v.addElement(Util.unquote(_path)); return v;} + byte[] dir; + if(i==0){dir=new byte[]{(byte)'/'};} + else + { + dir=new byte[i]; + java.System.arraycopy(path, 0, dir, 0, i); + } + //System.err.println("dir: "+new String(dir)); + byte[] pattern=new byte[path.Length-i-1]; + java.System.arraycopy(path, i+1, pattern, 0, pattern.Length); + //System.err.println("file: "+new String(pattern)); + + sendOPENDIR(dir); + + Header _header=new Header(); + _header=header(buf, _header); + int length=_header.length; + int type=_header.type; + buf.rewind(); + fill(buf.buffer, 0, length); + + if(type!=SSH_FXP_STATUS && type!=SSH_FXP_HANDLE) + { + throw new SftpException(SSH_FX_FAILURE, ""); + } + if(type==SSH_FXP_STATUS) + { + i=buf.getInt(); + throwStatusError(buf, i); + } + + byte[] handle=buf.getString(); // filename + + while(true) + { + sendREADDIR(handle); + _header=header(buf, _header); + length=_header.length; + type=_header.type; + + if(type!=SSH_FXP_STATUS && type!=SSH_FXP_NAME) + { + throw new SftpException(SSH_FX_FAILURE, ""); + } + if(type==SSH_FXP_STATUS) + { + buf.rewind(); + fill(buf.buffer, 0, length); + break; + } + + buf.rewind(); + fill(buf.buffer, 0, 4); length-=4; + int count=buf.getInt(); + + byte[] str; + int flags; + + buf.reset(); + while(count>0) + { + if(length>0) + { + buf.shift(); + int j=(buf.buffer.Length>(buf.index+length)) ? length : (buf.buffer.Length-buf.index); + i=io.ins.read(buf.buffer, buf.index, j); + if(i<=0)break; + buf.index+=i; + length-=i; + } + + byte[] filename=buf.getString(); + //System.err.println("filename: "+new String(filename)); + str=buf.getString(); + SftpATTRS attrs=SftpATTRS.getATTR(buf); + + if(Util.glob(pattern, filename)) + { + v.addElement(new String(dir)+"/"+new String(filename)); + } + count--; + } + } + if(_sendCLOSE(handle, _header)) + return v; + return null; + } + + private Vector glob_local(String _path) + { //throws Exception{ + //System.out.println("glob_local: "+_path); + Vector v=new Vector(); + byte[] path=_path.getBytes(); + int i=path.Length-1; + while(i>=0){if(path[i]=='*' || path[i]=='?')break;i--;} + if(i<0){ v.addElement(_path); return v;} + while(i>=0){if(path[i]==file_separatorc)break;i--;} + if(i<0){ v.addElement(_path); return v;} + byte[] dir; + if(i==0){dir=new byte[]{(byte)file_separatorc};} + else + { + dir=new byte[i]; + Tamir.SharpSsh.java.System.arraycopy(path, 0, dir, 0, i); + } + byte[] pattern=new byte[path.Length-i-1]; + Tamir.SharpSsh.java.System.arraycopy(path, i+1, pattern, 0, pattern.Length); + //System.out.println("dir: "+new String(dir)+" pattern: "+new String(pattern)); + try + { + String[] children=(new File(new String(dir))).list(); + for(int j=0; j=3) + { + byte[] str=buf.getString(); + //byte[] tag=buf.getString(); + throw new SftpException(i, new String(str)); + } + else + { + throw new SftpException(i, "Failure"); + } + } + + private static bool isLocalAbsolutePath(String path) + { + return (new File(path)).isAbsolute(); + } + + /* + public void finalize() { //throws Throwable{ + base.finalize(); + } + */ + + public override void disconnect() + { + //waitForRunningThreadFinish(10000); + clearRunningThreads(); + base.disconnect(); + } + private java.util.Vector threadList=null; + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)] + protected void addRunningThread(Thread thread) + { + if(threadList==null)threadList=new java.util.Vector(); + threadList.add(thread); + } + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)] + protected void clearRunningThreads() + { + if(threadList==null)return; + for(int t=0;t=0) + { + if(path[i]=='*' || path[i]=='?') + { + if(i>0 && path[i-1]=='\\') + { + i--; + } + else + { + break; + } + } + i--; + } + //System.err.println("isPattern: ["+(new String(path))+"] "+(!(i<0))); + return !(i<0); + } + + private int fill(byte[] buf, int s, int len) + { + int i=0; + int foo=s; + while(len>0) + { + i=io.ins.read(buf, s, len); + if(i<=0) + { + throw new System.IO.IOException("inputstream is closed"); + //return (s-foo)==0 ? i : s-foo; + } + s+=i; + len-=i; + } + return s-foo; + } + + //tamir: some functions from jsch-0.1.30 + private void skip(long foo) + { + while(foo>0) + { + long bar=io.ins.skip(foo); + if(bar<=0) + break; + foo-=bar; + } + } + + internal class Header + { + public int length; + public int type; + public int rid; + } + internal Header header(Buffer buf, Header header) + { + buf.rewind(); + int i=fill(buf.buffer, 0, 9); + header.length=buf.getInt()-5; + header.type=buf.getByte()&0xff; + header.rid=buf.getInt(); + return header; + } + + private String remoteAbsolutePath(String path) + { + if(path.charAt(0)=='/') return path; + if(cwd.endsWith("/")) return cwd+path; + return cwd+"/"+path; + } + + private String localAbsolutePath(String path) + { + if(isLocalAbsolutePath(path)) return path; + if(lcwd.endsWith(file_separator)) return lcwd+path; + return lcwd+file_separator+path; + } + + public class LsEntry + { + private String filename; + private String longname; + private SftpATTRS attrs; + internal LsEntry(String filename, String longname, SftpATTRS attrs) + { + setFilename(filename); + setLongname(longname); + setAttrs(attrs); + } + public String getFilename(){return filename;} + void setFilename(String filename){this.filename = filename;} + public String getLongname(){return longname;} + void setLongname(String longname){this.longname = longname;} + public SftpATTRS getAttrs(){return attrs;} + void setAttrs(SftpATTRS attrs) {this.attrs = attrs;} + public override string ToString(){return toString();} + public String toString(){ return longname; } + } + + public class InputStreamGet : InputStream + { + ChannelSftp sftp; + SftpProgressMonitor monitor; + long offset=0; + bool closed=false; + int rest_length=0; + byte[] _data=new byte[1]; + byte[] rest_byte=new byte[1024]; + byte[] handle; + Header header=new Header(); + + public InputStreamGet( + ChannelSftp sftp, + byte[] handle, + SftpProgressMonitor monitor) + { + this.sftp=sftp; + this.handle=handle; + this.monitor=monitor; + } + + public override int ReadByte() + { + if(closed)return -1; + int i=read(_data, 0, 1); + if (i==-1) { return -1; } + else + { + return _data[0]&0xff; + } + } + public int Read(byte[] d) + { + if(closed)return -1; + return Read(d, 0, d.Length); + } + public override int Read(byte[] d, int s, int len) + { + if(closed)return -1; + int i; + int foo; + if(d==null){throw new System.NullReferenceException();} + if(s<0 || len <0 || s+len>d.Length) + { + throw new System.IndexOutOfRangeException(); + } + if(len==0){ return 0; } + + if(rest_length>0) + { + foo=rest_length; + if(foo>len) foo=len; + java.System.arraycopy(rest_byte, 0, d, s, foo); + if(foo!=rest_length) + { + java.System.arraycopy(rest_byte, foo, + rest_byte, 0, rest_length-foo); + } + if(monitor!=null) + { + if(!monitor.count(foo)) + { + close(); + return -1; + } + } + + rest_length-=foo; + return foo; + } + + if(sftp.buf.buffer.Length-131024) + { + len=1024; + } + + try{sftp.sendREAD(handle, offset, len);} + catch(Exception e){ throw new System.IO.IOException("error"); } + + header= sftp.header(sftp.buf, header); + rest_length=header.length; + int type=header.type; + int id=header.rid; + + if(type!=SSH_FXP_STATUS && type!=SSH_FXP_DATA) + { + throw new System.IO.IOException("error"); + } + if(type==SSH_FXP_STATUS) + { + sftp.buf.rewind(); + sftp.fill(sftp.buf.buffer, 0, rest_length); + i=sftp.buf.getInt(); + rest_length=0; + if(i==SSH_FX_EOF) + { + close(); + return -1; + } + //throwStatusError(buf, i); + throw new System.IO.IOException("error"); + } + sftp.buf.rewind(); + sftp.fill(sftp.buf.buffer, 0, 4); + i=sftp.buf.getInt(); rest_length-=4; + + offset+=rest_length; + foo=i; + if(foo>0) + { + int bar=rest_length; + if(bar>len) + { + bar=len; + } + i=sftp.io.ins.read(d, s, bar); + if(i<0) + { + return -1; + } + rest_length-=i; + + if(rest_length>0) + { + if(rest_byte.Length0) + { + j=sftp.io.ins.read(rest_byte, _s, _len); + if(j<=0)break; + _s+=j; + _len-=j; + } + } + + if(monitor!=null) + { + if(!monitor.count(i)) + { + close(); + return -1; + } + } + return i; + } + return 0; // ?? + } + public override void Close() + { + if(closed)return; + closed=true; + /* + while(rest_length>0){ + int foo=rest_length; + if(foo>buf.buffer.length){ + foo=buf.buffer.length; + } + io.in.read(buf.buffer, 0, foo); + rest_length-=foo; + } + */ + if(monitor!=null)monitor.end(); + try{sftp._sendCLOSE(handle, header);} + catch(Exception e){throw new System.IO.IOException("error");} + } + } + } +} diff --git a/SharpSSH/jsch/ChannelSftpStreamGet.cs b/SharpSSH/jsch/ChannelSftpStreamGet.cs new file mode 100644 index 00000000..1a9b1473 --- /dev/null +++ b/SharpSSH/jsch/ChannelSftpStreamGet.cs @@ -0,0 +1,157 @@ +//using System; +//using System.IO; +// +//namespace Tamir.SharpSsh.jsch +//{ +// public class InputStreamGet : java.io.InputStream +// { +// bool closed=false; +// int rest_length=0; +// byte[] _data=new byte[1]; +// ChannelSftp sftp; +// byte[] handle; +// long[] _offset; +// int[]_server_version; +// SftpProgressMonitor monitor; +// +// public InputStreamGet( +// ChannelSftp sftp, +// byte[] handle, +// long[] _offset, +// int[] _server_version, +// SftpProgressMonitor monitor) +// { +// this.sftp=sftp; +// this.handle=handle; +// this._offset=_offset; +// this._server_version=_server_version; +// this.monitor=monitor; +// } +// +// public override int ReadByte() +// { +// int i=read(_data, 0, 1); +// if (i==-1) { return -1; } +// else +// { +// return _data[0]&0xff; +// } +// } +// public override int read(byte[] d) +// { +// return Read(d, 0, d.Length); +// } +// public override int Read(byte[] d, int s, int len) +// { +// if(d==null){throw new NullReferenceException();} +// if(s<0 || len <0 || s+len>d.Length) +// { +// throw new ArgumentOutOfRangeException(); +// } +// if(len==0){ return 0; } +// +// if(rest_length>0) +// { +// int foo=rest_length; +// if(foo>len) foo=len; +// int i=sftp.io.ins.read(d, s, foo); +// if(i<0) +// { +// throw new IOException("error"); +// } +// rest_length-=i; +// return i; +// } +// +// if(sftp.buf.buffer.length-131024) +// { +// len=1024; +// } +// +// try{sftp.sendREAD(handle, offset, len);} +// catch(Exception e){ throw new IOException("error"); } +// +// sftp.buf.rewind(); +// int i=io.ins.read(buf.buffer, 0, 13); // 4 + 1 + 4 + 4 +// if(i!=13) +// { +// throw new IOException("error"); +// } +// +// rest_length=sftp.buf.getInt(); +// int type=sftp.buf.getByte(); +// rest_length--; +// sftp.buf.getInt(); +// rest_length-=4; +// if(type!=sftp.SSH_FXP_STATUS && type!=SSH_FXP_DATA) +// { +// throw new IOException("error"); +// } +// if(type==sftp.SSH_FXP_STATUS) +// { +// i=buf.getInt(); +// rest_length-=4; +// sftp.io.ins.read(sftp.buf.buffer, 13, rest_length); +// rest_length=0; +// if(i==SSH_FX_EOF) +// { +// close(); +// return -1; +// } +// //throwStatusError(buf, i); +// throw new IOException("error"); +// } +// +// i=buf.getInt(); +// rest_length-=4; +// offset+=rest_length; +// int foo=i; +// if(foo>0) +// { +// int bar=rest_length; +// if(bar>len) +// { +// bar=len; +// } +// i=io.ins.read(d, s, bar); +// if(i<0) +// { +// return -1; +// } +// rest_length-=i; +// +// if(monitor!=null) +// { +// if(!monitor.count(i)) +// { +// return -1; +// } +// } +// return i; +// } +// return 0; // ?? +// } +// public override void Close() +// { +// if(closed)return; +// closed=true; +// /* +// while(rest_length>0){ +// int foo=rest_length; +// if(foo>buf.buffer.length){ +// foo=buf.buffer.length; +// } +// io.in.read(buf.buffer, 0, foo); +// rest_length-=foo; +// } +// */ +// if(monitor!=null)monitor.end(); +// try{sftp._sendCLOSE(handle);} +// catch(Exception e){throw new IOException("error");} +// } +// } +//} diff --git a/SharpSSH/jsch/ChannelSftpStreamPut.cs b/SharpSSH/jsch/ChannelSftpStreamPut.cs new file mode 100644 index 00000000..ac097e3c --- /dev/null +++ b/SharpSSH/jsch/ChannelSftpStreamPut.cs @@ -0,0 +1,116 @@ +using System; +using System.IO; + +namespace Tamir.SharpSsh.jsch +{ + internal class OutputStreamPut : java.io.OutputStream + { + ChannelSftp sftp; + byte[] handle; + long[] _offset; + SftpProgressMonitor monitor; + private bool init=true; + private int[] ackid=new int[1]; + private int startid=0; + private int _ackid=0; + private int ackcount=0; + private ChannelSftp.Header header=new ChannelSftp.Header(); + + internal OutputStreamPut + (ChannelSftp sftp, + byte[] handle, + long[] _offset, + SftpProgressMonitor monitor):base() + { + this.sftp=sftp; + this.handle=handle; + this._offset=_offset; + this.monitor=monitor; + } + + public override void Write(byte[] d, int s, int len) + { + if(init) + { + startid=sftp.seq; + _ackid=sftp.seq; + init=false; + } + + try + { + int _len=len; + while(_len>0) + { + int sent=sftp.sendWRITE(handle, _offset[0], d, s, _len); + _offset[0]+=sent; + s+=sent; + _len-=sent; + if((sftp.seq-1)==startid || + sftp.io.ins.available()>=1024) + { + while(sftp.io.ins.available()>0) + { + if(sftp.checkStatus(ackid, header)) + { + _ackid=ackid[0]; + if(startid>_ackid || _ackid>sftp.seq-1) + { + throw new SftpException(ChannelSftp.SSH_FX_FAILURE, ""); + } + ackcount++; + } + else + { + break; + } + } + } + } + if(monitor!=null && !monitor.count(len)) + { + close(); + throw new IOException("canceled"); + } + } + catch(IOException e){ throw e; } + catch(Exception e){ throw new IOException(e.ToString()); } + } + byte[] _data=new byte[1]; + public void write(int foo) + { + _data[0]=(byte)foo; + Write(_data, 0, 1); + } + public override void Close() + { + if(!init) + { + try + { + int _ackcount=sftp.seq-startid; + while(_ackcount>ackcount) + { + if(!sftp.checkStatus(null, header)) + { + break; + } + ackcount++; + } + } + catch(SftpException e) + { + throw new IOException(e.toString()); + } + } + + if(monitor!=null)monitor.end(); + try{ sftp._sendCLOSE(handle, header); } + catch(IOException e){ throw e; } + catch(Exception e) + { + throw new IOException(e.ToString()); + } + } + } +} diff --git a/SharpSSH/jsch/ChannelShell.cs b/SharpSSH/jsch/ChannelShell.cs new file mode 100644 index 00000000..4220458b --- /dev/null +++ b/SharpSSH/jsch/ChannelShell.cs @@ -0,0 +1,89 @@ +using System; +using Tamir.SharpSsh.java.lang; + +namespace Tamir.SharpSsh.jsch +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + public class ChannelShell : ChannelSession + { + internal bool xforwading=false; + internal bool pty=true; + public override void setXForwarding(bool foo){ xforwading=foo; } + public void setPty(bool foo){ pty=foo; } + public override void start() + { + try + { + Request request; + if(xforwading) + { + request=new RequestX11(); + request.request(session, this); + } + if(pty) + { + request=new RequestPtyReq(); + request.request(session, this); + } + request=new RequestShell(); + request.request(session, this); + } + catch//(Exception e) + { + throw new JSchException("ChannelShell"); + } + thread=new Thread(this); + thread.setName("Shell for "+session.host); + thread.start(); + } + public override void init() + { + io.setInputStream(session.In); + io.setOutputStream(session.Out); + } + + public void setPtySize(int col, int row, int wp, int hp) + { + //if(thread==null) return; + try + { + RequestWindowChange request=new RequestWindowChange(); + request.setSize(col, row, wp, hp); + request.request(session, this); + } + catch(Exception e) + { + throw new JSchException("ChannelShell.setPtySize: "+e.ToString()); + } + } + } + +} diff --git a/SharpSSH/jsch/ChannelSubsystem.cs b/SharpSSH/jsch/ChannelSubsystem.cs new file mode 100644 index 00000000..527433a5 --- /dev/null +++ b/SharpSSH/jsch/ChannelSubsystem.cs @@ -0,0 +1,89 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2005 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using Tamir.SharpSsh.java.net; +using Tamir.SharpSsh.java.lang; + +namespace Tamir.SharpSsh.jsch +{ + public class ChannelSubsystem : ChannelSession + { + bool xforwading=false; + bool pty=false; + bool want_reply=true; + String subsystem=""; + public override void setXForwarding(bool foo){ xforwading=true; } + public void setPty(bool foo){ pty=foo; } + public void setWantReply(bool foo){ want_reply=foo; } + public void setSubsystem(String foo){ subsystem=foo; } + public override void start() + { + try + { + Request request; + if(xforwading) + { + request=new RequestX11(); + request.request(session, this); + } + if(pty) + { + request=new RequestPtyReq(); + request.request(session, this); + } + request=new RequestSubsystem(); + ((RequestSubsystem)request).request(session, this, subsystem, want_reply); + } + catch(Exception e) + { + if(e is JSchException){ throw (JSchException)e; } + throw new JSchException("ChannelSubsystem"); + } + Thread thread=new Thread(this); + thread.setName("Subsystem for "+session.host); + thread.start(); + } + //public void finalize() throws Throwable{ super.finalize(); } + public override void init() + { + io.setInputStream(session.In); + io.setOutputStream(session.Out); + } + public void setErrStream(System.IO.Stream outs) + { + setExtOutputStream(outs); + } + public java.io.InputStream getErrStream() + { + return getExtInputStream(); + } + } + +} diff --git a/SharpSSH/jsch/ChannelX11.cs b/SharpSSH/jsch/ChannelX11.cs new file mode 100644 index 00000000..cd33875d --- /dev/null +++ b/SharpSSH/jsch/ChannelX11.cs @@ -0,0 +1,257 @@ +using System; +using System.Net; +using System.Net.Sockets; +using Tamir.SharpSsh.java.lang; + +namespace Tamir.SharpSsh.jsch +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + internal class ChannelX11 : Channel + { + + private const int LOCAL_WINDOW_SIZE_MAX=0x20000; + private const int LOCAL_MAXIMUM_PACKET_SIZE=0x4000; + + internal static String host="127.0.0.1"; + internal static int port=6000; + + internal bool _init=true; + + internal static byte[] cookie=null; + //static byte[] cookie_hex="0c281f065158632a427d3e074d79265d".getBytes(); + internal static byte[] cookie_hex=null; + + private static System.Collections.Hashtable faked_cookie_pool=new System.Collections.Hashtable(); + private static System.Collections.Hashtable faked_cookie_hex_pool=new System.Collections.Hashtable(); + + internal static byte[] table={0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39, + 0x61,0x62,0x63,0x64,0x65,0x66}; + internal static int revtable(byte foo) + { + for(int i=0; i>4)&0xf]; + bar[2*i+1]=table[(foo[i])&0xf]; + } + faked_cookie_hex_pool.Add(session, bar); + foo=bar; + } + return foo; + } + } + + Socket socket = null; + internal ChannelX11():base() + { + + setLocalWindowSizeMax(LOCAL_WINDOW_SIZE_MAX); + setLocalWindowSize(LOCAL_WINDOW_SIZE_MAX); + setLocalPacketSize(LOCAL_MAXIMUM_PACKET_SIZE); + + type=Util.getBytes("x11"); + try + { + IPEndPoint ep = new IPEndPoint(Dns.GetHostByName(host).AddressList[0], port); + socket=new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, 1); + socket.Connect(ep); + io=new IO(); + NetworkStream ns = new NetworkStream( socket ); + io.setInputStream(ns); + io.setOutputStream(ns); + } + catch(Exception e) + { + Console.WriteLine(e); + } + } + + public override void run() + { + thread=Thread.currentThread(); + Buffer buf=new Buffer(rmpsize); + Packet packet=new Packet(buf); + int i=0; + try + { + while(thread!=null) + { + i=io.ins.Read(buf.buffer, + 14, + buf.buffer.Length-14 + -16 -20 // padding and mac + ); + if(i<=0) + { + eof(); + break; + } + if(_close)break; + packet.reset(); + buf.putByte((byte)Session.SSH_MSG_CHANNEL_DATA); + buf.putInt(recipient); + buf.putInt(i); + buf.skip(i); + session.write(packet, this, i); + } + } + catch + { + //System.out.println(e); + } + thread=null; + } + + internal override void write(byte[] foo, int s, int l) + { + //if(eof_local)return; + + if(_init) + { + int plen=(foo[s+6]&0xff)*256+(foo[s+7]&0xff); + int dlen=(foo[s+8]&0xff)*256+(foo[s+9]&0xff); + if((foo[s]&0xff)==0x42) + { + } + else if((foo[s]&0xff)==0x6c) + { + plen=(int)(((uint)plen>>8)&0xff)|((plen<<8)&0xff00); + dlen=(int)(((uint)dlen>>8)&0xff)|((dlen<<8)&0xff00); + } + else + { + // ?? + } + byte[] bar=new byte[dlen]; + Array.Copy(foo, s+12+plen+((-plen)&3), bar, 0, dlen); + byte[] faked_cookie=(byte[])faked_cookie_pool[session]; + + if(equals(bar, faked_cookie)) + { + if(cookie!=null) + Array.Copy(cookie, 0, foo, s+12+plen+((-plen)&3), dlen); + } + else + { + Console.WriteLine("wrong cookie"); + } + _init=false; + } + io.put(foo, s, l); + } + + public override void disconnect() + { + close(); + thread=null; + try + { + if(io!=null) + { + try + { + if(io.ins!=null) + io.ins.Close(); + } + catch{} + try + { + if(io.outs!=null) + io.outs.Close(); + } + catch{} + } + try + { + if(socket!=null) + socket.Close(); + } + catch{} + } + catch(Exception e) + { + Console.WriteLine(e.StackTrace); + } + io=null; + Channel.del(this); + } + + private static bool equals(byte[] foo, byte[] bar) + { + if(foo.Length!=bar.Length)return false; + for(int i=0; i "); //dump(H, 0, H.length); + + i=0; + j=0; + j=(int)((K_S[i++]<<24)&0xff000000)|((K_S[i++]<<16)&0x00ff0000)| + ((K_S[i++]<<8)&0x0000ff00)|((K_S[i++])&0x000000ff); + String alg=Util.getString(K_S, i, j); + i+=j; + + result=false; + + if(alg.Equals("ssh-rsa")) + { + byte[] tmp; + byte[] ee; + byte[] n; + + type=RSA; + + j=(int)((K_S[i++]<<24)&0xff000000)|((K_S[i++]<<16)&0x00ff0000)| + ((K_S[i++]<<8)&0x0000ff00)|((K_S[i++])&0x000000ff); + tmp=new byte[j]; Array.Copy(K_S, i, tmp, 0, j); i+=j; + ee=tmp; + j=(int)((K_S[i++]<<24)&0xff000000)|((K_S[i++]<<16)&0x00ff0000)| + ((K_S[i++]<<8)&0x0000ff00)|((K_S[i++])&0x000000ff); + tmp=new byte[j]; Array.Copy(K_S, i, tmp, 0, j); i+=j; + n=tmp; + + // SignatureRSA sig=new SignatureRSA(); + // sig.init(); + + SignatureRSA sig=null; + try + { + Type t=Type.GetType(session.getConfig("signature.rsa")); + sig=(SignatureRSA)(Activator.CreateInstance(t)); + sig.init(); + } + catch(Exception eee) + { + Console.WriteLine(eee); + } + + sig.setPubKey(ee, n); + sig.update(H); + result=sig.verify(sig_of_H); + //MainClass.dump(ee, n, sig_of_H, H); + } + else if(alg.Equals("ssh-dss")) + { + byte[] q=null; + byte[] tmp; + byte[] p; + byte[] g; + + type=DSS; + + j=(int)((K_S[i++]<<24)&0xff000000)|((K_S[i++]<<16)&0x00ff0000)| + ((K_S[i++]<<8)&0x0000ff00)|((K_S[i++])&0x000000ff); + tmp=new byte[j]; Array.Copy(K_S, i, tmp, 0, j); i+=j; + p=tmp; + j=(int)((K_S[i++]<<24)&0xff000000)|((K_S[i++]<<16)&0x00ff0000)| + ((K_S[i++]<<8)&0x0000ff00)|((K_S[i++])&0x000000ff); + tmp=new byte[j]; Array.Copy(K_S, i, tmp, 0, j); i+=j; + q=tmp; + j=(int)((K_S[i++]<<24)&0xff000000)|((K_S[i++]<<16)&0x00ff0000)| + ((K_S[i++]<<8)&0x0000ff00)|((K_S[i++])&0x000000ff); + tmp=new byte[j]; Array.Copy(K_S, i, tmp, 0, j); i+=j; + g=tmp; + j=(int)((K_S[i++]<<24)&0xff000000)|((K_S[i++]<<16)&0x00ff0000)| + ((K_S[i++]<<8)&0x0000ff00)|((K_S[i++])&0x000000ff); + tmp=new byte[j]; Array.Copy(K_S, i, tmp, 0, j); i+=j; + f=tmp; + // SignatureDSA sig=new SignatureDSA(); + // sig.init(); + SignatureDSA sig=null; + try + { + Type t=Type.GetType(session.getConfig("signature.dss")); + sig=(SignatureDSA)(Activator.CreateInstance(t)); + sig.init(); + } + catch(Exception ee) + { + Console.WriteLine(ee); + } + sig.setPubKey(f, p, q, g); + sig.update(H); + result=sig.verify(sig_of_H); + } + else + { + Console.WriteLine("unknow alg"); + } + state=STATE_END; + break; + } + return result; + } + + public override String getKeyType() + { + if(type==DSS) return "DSA"; + return "RSA"; + } + + public override int getState(){return state; } + } + +} diff --git a/SharpSSH/jsch/DHGEX.cs b/SharpSSH/jsch/DHGEX.cs new file mode 100644 index 00000000..922b459c --- /dev/null +++ b/SharpSSH/jsch/DHGEX.cs @@ -0,0 +1,340 @@ +using System; + +namespace Tamir.SharpSsh.jsch +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + + public class DHGEX : KeyExchange + { + + internal const int SSH_MSG_KEX_DH_GEX_GROUP= 31; + internal const int SSH_MSG_KEX_DH_GEX_INIT= 32; + internal const int SSH_MSG_KEX_DH_GEX_REPLY= 33; + + internal static int min=1024; + + // static int min=512; + internal static int preferred=1024; + internal static int max=1024; + + // static int preferred=1024; + // static int max=2000; + + internal const int RSA=0; + internal const int DSS=1; + private int type=0; + + private int state; + + // com.jcraft.jsch.DH dh; + internal DH dh; + + internal byte[] V_S; + internal byte[] V_C; + internal byte[] I_S; + internal byte[] I_C; + + private Buffer buf; + private Packet packet; + + private byte[] p; + private byte[] g; + private byte[] e; + //private byte[] f; + + public override void init(Session session, + byte[] V_S, byte[] V_C, byte[] I_S, byte[] I_C) + { + this.session=session; + this.V_S=V_S; + this.V_C=V_C; + this.I_S=I_S; + this.I_C=I_C; + + // sha=new SHA1(); + // sha.init(); + + try + { + Type t=Type.GetType(session.getConfig("sha-1")); + sha=(HASH)(Activator.CreateInstance(t)); + sha.init(); + } + catch(Exception e) + { + Console.WriteLine(e); + } + + buf=new Buffer(); + packet=new Packet(buf); + + try + { + Type t=Type.GetType(session.getConfig("dh")); + dh=(DH)(Activator.CreateInstance(t)); + dh.init(); + } + catch(Exception e) + { + throw e; + } + + packet.reset(); + buf.putByte((byte)0x22); + buf.putInt(min); + buf.putInt(preferred); + buf.putInt(max); + session.write(packet); + + state=SSH_MSG_KEX_DH_GEX_GROUP; + } + + public override bool next(Buffer _buf) + { + int i,j; + bool result=false; + switch(state) + { + case SSH_MSG_KEX_DH_GEX_GROUP: + // byte SSH_MSG_KEX_DH_GEX_GROUP(31) + // mpint p, safe prime + // mpint g, generator for subgroup in GF (p) + _buf.getInt(); + _buf.getByte(); + j=_buf.getByte(); + if(j!=31) + { + Console.WriteLine("type: must be 31 "+j); + result = false; + } + + p=_buf.getMPInt(); + g=_buf.getMPInt(); + /* + for(int iii=0; iii "); dump(H, 0, H.length); + + i=0; + j=0; + j=(int)((K_S[i++]<<24)&0xff000000)|((K_S[i++]<<16)&0x00ff0000)| + ((K_S[i++]<<8)&0x0000ff00)|((K_S[i++])&0x000000ff); + String alg=Util.getString(K_S, i, j); + i+=j; + + + if(alg.Equals("ssh-rsa")) + { + byte[] tmp; + byte[] ee; + byte[] n; + + type=RSA; + + j=(int)((K_S[i++]<<24)&0xff000000)|((K_S[i++]<<16)&0x00ff0000)| + ((K_S[i++]<<8)&0x0000ff00)|((K_S[i++])&0x000000ff); + tmp=new byte[j]; Array.Copy(K_S, i, tmp, 0, j); i+=j; + ee=tmp; + j=(int)((K_S[i++]<<24)&0xff000000)|((K_S[i++]<<16)&0x00ff0000)| + ((K_S[i++]<<8)&0x0000ff00)|((K_S[i++])&0x000000ff); + tmp=new byte[j]; Array.Copy(K_S, i, tmp, 0, j); i+=j; + n=tmp; + + // SignatureRSA sig=new SignatureRSA(); + // sig.init(); + + SignatureRSA sig=null; + try + { + Type t=Type.GetType(session.getConfig("signature.rsa")); + sig=(SignatureRSA)(Activator.CreateInstance(t)); + sig.init(); + } + catch(Exception eee) + { + Console.WriteLine(eee); + } + + sig.setPubKey(ee, n); + sig.update(H); + result=sig.verify(sig_of_H); + } + else if(alg.Equals("ssh-dss")) + { + byte[] q=null; + byte[] tmp; + + type=DSS; + + j=(int)((K_S[i++]<<24)&0xff000000)|((K_S[i++]<<16)&0x00ff0000)| + ((K_S[i++]<<8)&0x0000ff00)|((K_S[i++])&0x000000ff); + tmp=new byte[j]; Array.Copy(K_S, i, tmp, 0, j); i+=j; + p=tmp; + j=(int)((K_S[i++]<<24)&0xff000000)|((K_S[i++]<<16)&0x00ff0000)| + ((K_S[i++]<<8)&0x0000ff00)|((K_S[i++])&0x000000ff); + tmp=new byte[j]; Array.Copy(K_S, i, tmp, 0, j); i+=j; + q=tmp; + j=(int)((K_S[i++]<<24)&0xff000000)|((K_S[i++]<<16)&0x00ff0000)| + ((K_S[i++]<<8)&0x0000ff00)|((K_S[i++])&0x000000ff); + tmp=new byte[j]; Array.Copy(K_S, i, tmp, 0, j); i+=j; + g=tmp; + j=(int)((K_S[i++]<<24)&0xff000000)|((K_S[i++]<<16)&0x00ff0000)| + ((K_S[i++]<<8)&0x0000ff00)|((K_S[i++])&0x000000ff); + tmp=new byte[j]; Array.Copy(K_S, i, tmp, 0, j); i+=j; + f=tmp; + + // SignatureDSA sig=new SignatureDSA(); + // sig.init(); + + SignatureDSA sig=null; + try + { + Type t=Type.GetType(session.getConfig("signature.dss")); + sig=(SignatureDSA)(Activator.CreateInstance(t)); + sig.init(); + } + catch(Exception ee) + { + Console.WriteLine(ee); + } + + sig.setPubKey(f, p, q, g); + sig.update(H); + result=sig.verify(sig_of_H); + } + else + { + Console.WriteLine("unknow alg"); + } + state=STATE_END; + break; + } + return result; + } + + public override String getKeyType() + { + if(type==DSS) return "DSA"; + return "RSA"; + } + + public override int getState(){return state; } + } + +} + diff --git a/SharpSSH/jsch/ForwardedTCPIPDaemon.cs b/SharpSSH/jsch/ForwardedTCPIPDaemon.cs new file mode 100644 index 00000000..5ec781ab --- /dev/null +++ b/SharpSSH/jsch/ForwardedTCPIPDaemon.cs @@ -0,0 +1,11 @@ +using System; +using Tamir.SharpSsh.java.lang; + +namespace Tamir.SharpSsh.jsch +{ + public interface ForwardedTCPIPDaemon : Runnable + { + void setChannel(ChannelForwardedTCPIP channel); + void setArg(Object[] arg); + } +} diff --git a/SharpSSH/jsch/HASH.cs b/SharpSSH/jsch/HASH.cs new file mode 100644 index 00000000..33ebcecf --- /dev/null +++ b/SharpSSH/jsch/HASH.cs @@ -0,0 +1,41 @@ +using System; + +namespace Tamir.SharpSsh.jsch +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + public abstract class HASH + { + public abstract void init(); + public abstract int getBlockSize(); + public abstract void update(byte[] foo, int start, int len); + public abstract byte[] digest(); + } +} diff --git a/SharpSSH/jsch/HostKey.cs b/SharpSSH/jsch/HostKey.cs new file mode 100644 index 00000000..6c5042f6 --- /dev/null +++ b/SharpSSH/jsch/HostKey.cs @@ -0,0 +1,74 @@ +using System; + +namespace Tamir.SharpSsh.jsch +{ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + public class HostKey{ + private static byte[] sshdss= System.Text.Encoding.Default.GetBytes( "ssh-dss" ); + private static byte[] sshrsa= System.Text.Encoding.Default.GetBytes( "ssh-rsa" ); + + public const int SSHDSS=0; + public const int SSHRSA=1; + public const int UNKNOWN=2; + + internal String host; + internal int type; + internal byte[] key; + public HostKey(String host, byte[] key) + { + this.host=host; this.key=key; + if(key[8]=='d'){ this.type=SSHDSS; } + else if(key[8]=='r'){ this.type=SSHRSA; } + else { throw new JSchException("invalid key type");} + } + internal HostKey(String host, int type, byte[] key){ + this.host=host; this.type=type; this.key=key; + } + public String getHost(){ return host; } + public String getType(){ + if(type==SSHDSS){ return System.Text.Encoding.Default.GetString(sshdss); } + if(type==SSHRSA){ return System.Text.Encoding.Default.GetString(sshrsa);} + return "UNKNOWN"; + } + public String getKey(){ + return Convert.ToBase64String(key, 0, key.Length); + } + public String getFingerPrint(JSch jsch){ + HASH hash=null; + try{ + hash=(HASH)Activator.CreateInstance(Type.GetType(jsch.getConfig("md5"))); + } + catch(Exception e){ Console.Error.WriteLine("getFingerPrint: "+e); } + return Util.getFingerPrint(hash, key); + } + } + +} diff --git a/SharpSSH/jsch/HostKeyRepository.cs b/SharpSSH/jsch/HostKeyRepository.cs new file mode 100644 index 00000000..aff4fa30 --- /dev/null +++ b/SharpSSH/jsch/HostKeyRepository.cs @@ -0,0 +1,48 @@ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; + +namespace Tamir.SharpSsh.jsch +{ + public abstract class HostKeyRepository + { + internal const int OK=0; + internal const int NOT_INCLUDED=1; + internal const int CHANGED=2; + + public abstract int check(String host, byte[] key); + public abstract void add(String host, byte[] key, UserInfo ui); + public abstract void remove(String host, String type); + public abstract void remove(String host, String type, byte[] key); + public abstract String getKnownHostsRepositoryID(); + public abstract HostKey[] getHostKey(); + public abstract HostKey[] getHostKey(String host, String type); + } +} diff --git a/SharpSSH/jsch/IO.cs b/SharpSSH/jsch/IO.cs new file mode 100644 index 00000000..a54d7856 --- /dev/null +++ b/SharpSSH/jsch/IO.cs @@ -0,0 +1,183 @@ +using System; +using System.IO; +using Tamir.SharpSsh.java.io; + +namespace Tamir.SharpSsh.jsch +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + public class IO + { + internal JStream ins; + internal JStream outs; + internal JStream outs_ext; + + private bool in_dontclose=false; + private bool out_dontclose=false; + private bool outs_ext_dontclose=false; + + public void setOutputStream(Stream outs) + { + if(outs!=null) + { + this.outs= new JStream(outs); + } + else + { + this.outs=null; + } + } + public void setOutputStream(Stream outs, bool dontclose) + { + this.out_dontclose=dontclose; + setOutputStream(outs); + } + public void setExtOutputStream(Stream outs) + { + if(outs!=null) + { + this.outs_ext=new JStream(outs); + } + else + { + this.outs_ext=null; + } + } + public void setExtOutputStream(Stream outs, bool dontclose) + { + this.outs_ext_dontclose=dontclose; + setExtOutputStream(outs); + } + public void setInputStream(Stream ins) + { + //ConsoleStream low buffer patch + if(ins!=null) + { + if(ins.GetType() == Type.GetType("System.IO.__ConsoleStream")) + { + ins = new Tamir.Streams.ProtectedConsoleStream(ins); + } + else if(ins.GetType() == Type.GetType("System.IO.FileStream")) + { + ins = new Tamir.Streams.ProtectedConsoleStream(ins); + } + this.ins=new JStream(ins); + } + else + { + this.ins=null; + } + } + public void setInputStream(Stream ins, bool dontclose) + { + this.in_dontclose=dontclose; + setInputStream(ins); + } + + public void put(Packet p) + { + outs.Write(p.buffer.buffer, 0, p.buffer.index); + outs.Flush(); + } + internal void put(byte[] array, int begin, int length) + { + outs.Write(array, begin, length); + outs.Flush(); + } + internal void put_ext(byte[] array, int begin, int length) + { + outs_ext.Write(array, begin, length); + outs_ext.Flush(); + } + + internal int getByte() + { + int res = ins.ReadByte()&0xff; + return res; + } + + internal void getByte(byte[] array) + { + getByte(array, 0, array.Length); + } + + internal void getByte(byte[] array, int begin, int length) + { + do + { + int completed = ins.Read(array, begin, length); + if(completed<=0) + { + throw new IOException("End of IO Stream Read"); + } + begin+=completed; + length-=completed; + } + while (length>0); + } + + public void close() + { + try + { + if(ins!=null && !in_dontclose) ins.Close(); + ins=null; + } + catch(Exception ee){} + try + { + if(outs!=null && !out_dontclose) outs.Close(); + outs=null; + } + catch(Exception ee){} + try + { + if(outs_ext!=null && !outs_ext_dontclose) outs_ext.Close(); + outs_ext=null; + } + catch(Exception ee){} + } + +// public void finalize() +// { +// try +// { +// if(ins!=null) ins.Close(); +// } +// catch{} +// try +// { +// if(outs!=null) outs.Close(); +// } +// catch{} +// } + } + +} diff --git a/SharpSSH/jsch/Identity.cs b/SharpSSH/jsch/Identity.cs new file mode 100644 index 00000000..a4e2c693 --- /dev/null +++ b/SharpSSH/jsch/Identity.cs @@ -0,0 +1,44 @@ +using System; + +namespace Tamir.SharpSsh.jsch +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + internal interface Identity + { + bool setPassphrase(String passphrase); + byte[] getPublicKeyBlob(); + byte[] getSignature(Session session, byte[] data); + bool decrypt(); + String getAlgName(); + String getName(); + bool isEncrypted(); + } +} diff --git a/SharpSSH/jsch/IdentityFile.cs b/SharpSSH/jsch/IdentityFile.cs new file mode 100644 index 00000000..51ecb948 --- /dev/null +++ b/SharpSSH/jsch/IdentityFile.cs @@ -0,0 +1,949 @@ +using System; +using System.IO; + +namespace Tamir.SharpSsh.jsch +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + internal class IdentityFile : Identity + { + String identity; + byte[] key; + byte[] iv; + private JSch jsch; + private HASH hash; + private byte[] encoded_data; + + private Cipher cipher; + + // DSA + private byte[] P_array; + private byte[] Q_array; + private byte[] G_array; + private byte[] pub_array; + private byte[] prv_array; + + // RSA + private byte[] n_array; // modulus + private byte[] e_array; // public exponent + private byte[] d_array; // private exponent + + private byte[] p_array; + private byte[] q_array; + private byte[] dmp1_array; + private byte[] dmq1_array; + private byte[] iqmp_array; + + // private String algname="ssh-dss"; + private String algname="ssh-rsa"; + + private const int ERROR=0; + private const int RSA=1; + private const int DSS=2; + internal const int UNKNOWN=3; + + private const int OPENSSH=0; + private const int FSECURE=1; + private const int PUTTY=2; + + private int type=ERROR; + private int keytype=OPENSSH; + + private byte[] publickeyblob=null; + + private bool encrypted=true; + + internal IdentityFile(String identity, JSch jsch) + { + this.identity=identity; + this.jsch=jsch; + try + { + Type c=Type.GetType(jsch.getConfig("3des-cbc")); + cipher=(Cipher)Activator.CreateInstance(c); + key=new byte[cipher.getBlockSize()]; // 24 + iv=new byte[cipher.getIVSize()]; // 8 + c=Type.GetType(jsch.getConfig("md5")); + hash=(HASH)(Activator.CreateInstance(c)); + hash.init(); + FileInfo file=new FileInfo(identity); + FileStream fis = File.OpenRead(identity); + byte[] buf=new byte[(int)(file.Length)]; + int len=fis.Read(buf, 0, buf.Length); + fis.Close(); + + int i=0; + while(i4 && // FSecure + encoded_data[0]==(byte)0x3f && + encoded_data[1]==(byte)0x6f && + encoded_data[2]==(byte)0xf9 && + encoded_data[3]==(byte)0xeb) + { + + Buffer _buf=new Buffer(encoded_data); + _buf.getInt(); // 0x3f6ff9be + _buf.getInt(); + byte[]_type=_buf.getString(); + //System.out.println("type: "+new String(_type)); + byte[] _cipher=_buf.getString(); + String s_cipher=System.Text.Encoding.Default.GetString(_cipher); + //System.out.println("cipher: "+cipher); + if(s_cipher.Equals("3des-cbc")) + { + _buf.getInt(); + byte[] foo=new byte[encoded_data.Length-_buf.getOffSet()]; + _buf.getByte(foo); + encoded_data=foo; + encrypted=true; + throw new JSchException("unknown privatekey format: "+identity); + } + else if(s_cipher.Equals("none")) + { + _buf.getInt(); + //_buf.getInt(); + + encrypted=false; + + byte[] foo=new byte[encoded_data.Length-_buf.getOffSet()]; + _buf.getByte(foo); + encoded_data=foo; + } + + } + + try + { + file=new FileInfo(identity+".pub"); + fis=File.OpenRead(identity+".pub"); + buf=new byte[(int)(file.Length)]; + len=fis.Read(buf, 0, buf.Length); + fis.Close(); + } + catch + { + return; + } + + if(buf.Length>4 && // FSecure's public key + buf[0]=='-' && buf[1]=='-' && buf[2]=='-' && buf[3]=='-') + { + + i=0; + do{i++;}while(buf.Length>i && buf[i]!=0x0a); + if(buf.Length<=i) return; + + while(true) + { + if(buf[i]==0x0a) + { + bool inheader=false; + for(int j=i+1; j=len) return; + start=i; + while(i>>24); + goo[1]=(byte)(session.getSessionId().Length>>>16); + goo[2]=(byte)(session.getSessionId().Length>>>8); + goo[3]=(byte)(session.getSessionId().Length); + rsa.update(goo); + rsa.update(session.getSessionId()); + */ + rsa.update(data); + byte[] sig = rsa.sign(); + Buffer buf=new Buffer("ssh-rsa".Length+4+ + sig.Length+4); + buf.putString( System.Text.Encoding.Default.GetBytes( "ssh-rsa" )); + buf.putString(sig); + return buf.buffer; + } + catch(Exception e) + { + Console.WriteLine(e); + } + return null; + } + + byte[] getSignature_dss(Session session, byte[] data) + { + /* + byte[] foo; + int i; + System.out.print("P "); + foo=P_array; + for(i=0; i>>24); + goo[1]=(byte)(session.getSessionId().Length>>>16); + goo[2]=(byte)(session.getSessionId().Length>>>8); + goo[3]=(byte)(session.getSessionId().Length); + dsa.update(goo); + dsa.update(session.getSessionId()); + */ + dsa.update(data); + byte[] sig = dsa.sign(); + Buffer buf=new Buffer("ssh-dss".Length+4+ + sig.Length+4); + buf.putString( System.Text.Encoding.Default.GetBytes( "ssh-dss" ) ); + buf.putString(sig); + return buf.buffer; + } + catch(Exception e) + { + Console.WriteLine("e "+e); + } + return null; + } + + public bool decrypt() + { + if(type==RSA) return decrypt_rsa(); + return decrypt_dss(); + } + + bool decrypt_rsa() + { +// byte[] p_array; +// byte[] q_array; +// byte[] dmp1_array; +// byte[] dmq1_array; +// byte[] iqmp_array; + + try + { + byte[] plain; + if(encrypted) + { + if(keytype==OPENSSH) + { + cipher.init(Cipher.DECRYPT_MODE, key, iv); + plain=new byte[encoded_data.Length]; + cipher.update(encoded_data, 0, encoded_data.Length, plain, 0); + } + else if(keytype==FSECURE) + { + for(int i=0; i0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + + if(plain[index]!=0x02)return false; + index++; // INTEGER + Length=plain[index++]&0xff; + if((Length&0x80)!=0) + { + int foo=Length&0x7f; Length=0; + while(foo-->0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + index+=Length; + + //System.out.println("int: len="+Length); + //System.out.print(Integer.toHexString(plain[index-1]&0xff)+":"); + //System.out.println(""); + + index++; + Length=plain[index++]&0xff; + if((Length&0x80)!=0) + { + int foo=Length&0x7f; Length=0; + while(foo-->0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + n_array=new byte[Length]; + Array.Copy(plain, index, n_array, 0, Length); + index+=Length; + /* + System.out.println("int: N len="+Length); + for(int i=0; i0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + e_array=new byte[Length]; + Array.Copy(plain, index, e_array, 0, Length); + index+=Length; + /* + System.out.println("int: E len="+Length); + for(int i=0; i0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + d_array=new byte[Length]; + Array.Copy(plain, index, d_array, 0, Length); + index+=Length; + /* + System.out.println("int: D len="+Length); + for(int i=0; i0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + p_array=new byte[Length]; + Array.Copy(plain, index, p_array, 0, Length); + index+=Length; + /* + System.out.println("int: P len="+Length); + for(int i=0; i0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + q_array=new byte[Length]; + Array.Copy(plain, index, q_array, 0, Length); + index+=Length; + /* + System.out.println("int: q len="+Length); + for(int i=0; i0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + dmp1_array=new byte[Length]; + Array.Copy(plain, index, dmp1_array, 0, Length); + index+=Length; + /* + System.out.println("int: dmp1 len="+Length); + for(int i=0; i0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + dmq1_array=new byte[Length]; + Array.Copy(plain, index, dmq1_array, 0, Length); + index+=Length; + /* + System.out.println("int: dmq1 len="+Length); + for(int i=0; i0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + iqmp_array=new byte[Length]; + Array.Copy(plain, index, iqmp_array, 0, Length); + index+=Length; + /* + System.out.println("int: iqmp len="+Length); + for(int i=0; i0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + + if(plain[index]!=0x02)return false; + index++; // INTEGER + Length=plain[index++]&0xff; + if((Length&0x80)!=0) + { + int foo=Length&0x7f; Length=0; + while(foo-->0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + index+=Length; + + index++; + Length=plain[index++]&0xff; + if((Length&0x80)!=0) + { + int foo=Length&0x7f; Length=0; + while(foo-->0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + P_array=new byte[Length]; + Array.Copy(plain, index, P_array, 0, Length); + index+=Length; + + index++; + Length=plain[index++]&0xff; + if((Length&0x80)!=0) + { + int foo=Length&0x7f; Length=0; + while(foo-->0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + Q_array=new byte[Length]; + Array.Copy(plain, index, Q_array, 0, Length); + index+=Length; + + index++; + Length=plain[index++]&0xff; + if((Length&0x80)!=0) + { + int foo=Length&0x7f; Length=0; + while(foo-->0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + G_array=new byte[Length]; + Array.Copy(plain, index, G_array, 0, Length); + index+=Length; + + index++; + Length=plain[index++]&0xff; + if((Length&0x80)!=0) + { + int foo=Length&0x7f; Length=0; + while(foo-->0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + pub_array=new byte[Length]; + Array.Copy(plain, index, pub_array, 0, Length); + index+=Length; + + index++; + Length=plain[index++]&0xff; + if((Length&0x80)!=0) + { + int foo=Length&0x7f; Length=0; + while(foo-->0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + prv_array=new byte[Length]; + Array.Copy(plain, index, prv_array, 0, Length); + index+=Length; + } + catch + { + //System.out.println(e); + //e.printStackTrace(); + return false; + } + return true; + } + + public bool isEncrypted() + { + return encrypted; + } + public String getName(){return identity;} + + private int writeSEQUENCE(byte[] buf, int index, int len) + { + buf[index++]=0x30; + index=writeLength(buf, index, len); + return index; + } + private int writeINTEGER(byte[] buf, int index, byte[] data) + { + buf[index++]=0x02; + index=writeLength(buf, index, data.Length); + Array.Copy(data, 0, buf, index, data.Length); + index+=data.Length; + return index; + } + + private int countLength(int i_len) + { + uint len = (uint)i_len; + int i=1; + if(len<=0x7f) return i; + while(len>0) + { + len>>=8; + i++; + } + return i; + } + + private int writeLength(byte[] data, int index, int i_len) + { + int len = (int)i_len; + int i=countLength(len)-1; + if(i==0) + { + data[index++]=(byte)len; + return index; + } + data[index++]=(byte)(0x80|i); + int j=index+i; + while(i>0) + { + data[index+i-1]=(byte)(len&0xff); + len>>=8; + i--; + } + return j; + } + + private byte a2b(byte c) + { + if('0'<=c&&c<='9') return (byte)(c-'0'); + if('a'<=c&&c<='z') return (byte)(c-'a'+10); + return (byte)(c-'A'+10); + } + private byte b2a(byte c) + { + if(0<=c&&c<=9) return (byte)(c+'0'); + return (byte)(c-10+'A'); + } + } + +} diff --git a/SharpSSH/jsch/JSch.cs b/SharpSSH/jsch/JSch.cs new file mode 100644 index 00000000..a320e482 --- /dev/null +++ b/SharpSSH/jsch/JSch.cs @@ -0,0 +1,241 @@ +using System; +using System.IO; + +namespace Tamir.SharpSsh.jsch +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + public class JSch + { + + static System.Collections.Hashtable config; + + public static void Init() + { + config=new System.Collections.Hashtable(); + + // config.Add("kex", "diffie-hellman-group-exchange-sha1"); + config.Add("kex", "diffie-hellman-group1-sha1,diffie-hellman-group-exchange-sha1"); + config.Add("server_host_key", "ssh-rsa,ssh-dss"); + //config.Add("server_host_key", "ssh-dss,ssh-rsa"); + + // config.Add("cipher.s2c", "3des-cbc,blowfish-cbc"); + // config.Add("cipher.c2s", "3des-cbc,blowfish-cbc"); + + config.Add("cipher.s2c", "3des-cbc,aes128-cbc"); + config.Add("cipher.c2s", "3des-cbc,aes128-cbc"); + + // config.Add("mac.s2c", "hmac-md5,hmac-sha1,hmac-sha1-96,hmac-md5-96"); + // config.Add("mac.c2s", "hmac-md5,hmac-sha1,hmac-sha1-96,hmac-md5-96"); + config.Add("mac.s2c", "hmac-md5,hmac-sha1"); + config.Add("mac.c2s", "hmac-md5,hmac-sha1"); + config.Add("compression.s2c", "none"); + config.Add("compression.c2s", "none"); + config.Add("lang.s2c", ""); + config.Add("lang.c2s", ""); + + config.Add("diffie-hellman-group-exchange-sha1", + "Tamir.SharpSsh.jsch.DHGEX"); + config.Add("diffie-hellman-group1-sha1", + "Tamir.SharpSsh.jsch.DHG1"); + + config.Add("dh", "Tamir.SharpSsh.jsch.jce.DH"); + config.Add("3des-cbc", "Tamir.SharpSsh.jsch.jce.TripleDESCBC"); + //config.Add("blowfish-cbc", "Tamir.SharpSsh.jsch.jce.BlowfishCBC"); + config.Add("hmac-sha1", "Tamir.SharpSsh.jsch.jce.HMACSHA1"); + config.Add("hmac-sha1-96", "Tamir.SharpSsh.jsch.jce.HMACSHA196"); + config.Add("hmac-md5", "Tamir.SharpSsh.jsch.jce.HMACMD5"); + config.Add("hmac-md5-96", "Tamir.SharpSsh.jsch.jce.HMACMD596"); + config.Add("sha-1", "Tamir.SharpSsh.jsch.jce.SHA1"); + config.Add("md5", "Tamir.SharpSsh.jsch.jce.MD5"); + config.Add("signature.dss", "Tamir.SharpSsh.jsch.jce.SignatureDSA"); + config.Add("signature.rsa", "Tamir.SharpSsh.jsch.jce.SignatureRSA"); + config.Add("keypairgen.dsa", "Tamir.SharpSsh.jsch.jce.KeyPairGenDSA"); + config.Add("keypairgen.rsa", "Tamir.SharpSsh.jsch.jce.KeyPairGenRSA"); + config.Add("random", "Tamir.SharpSsh.jsch.jce.Random"); + + config.Add("aes128-cbc", "Tamir.SharpSsh.jsch.jce.AES128CBC"); + + //config.Add("zlib", "com.jcraft.jsch.jcraft.Compression"); + + config.Add("StrictHostKeyChecking", "ask"); + } + + internal Tamir.SharpSsh.java.util.Vector pool=new Tamir.SharpSsh.java.util.Vector(); + internal Tamir.SharpSsh.java.util.Vector identities=new Tamir.SharpSsh.java.util.Vector(); + //private KnownHosts known_hosts=null; + private HostKeyRepository known_hosts=null; + + public JSch() + { + //known_hosts=new KnownHosts(this); + if (config==null) + Init(); + } + + public Session getSession(String username, String host) { return getSession(username, host, 22); } + public Session getSession(String username, String host, int port) + { + Session s=new Session(this); + s.setUserName(username); + s.setHost(host); + s.setPort(port); + pool.Add(s); + return s; + } + + internal bool removeSession(Session session) + { + lock(pool) + { + return pool.remove(session); + } + } + + public void setHostKeyRepository(HostKeyRepository foo) + { + known_hosts=foo; + } + public void setKnownHosts(String foo) + { + if(known_hosts==null) known_hosts=new KnownHosts(this); + if(known_hosts is KnownHosts) + { + lock(known_hosts) + { + ((KnownHosts)known_hosts).setKnownHosts(foo); + } + } + } + public void setKnownHosts(StreamReader foo) + { + if(known_hosts==null) known_hosts=new KnownHosts(this); + if(known_hosts is KnownHosts) + { + lock(known_hosts) + { + ((KnownHosts)known_hosts).setKnownHosts(foo); + } + } + } + /* + HostKeyRepository getKnownHosts(){ + if(known_hosts==null) known_hosts=new KnownHosts(this); + return known_hosts; + } + */ + public HostKeyRepository getHostKeyRepository() + { + if(known_hosts==null) known_hosts=new KnownHosts(this); + return known_hosts; + } + /* + public HostKey[] getHostKey(){ + if(known_hosts==null) return null; + return known_hosts.getHostKey(); + } + public void removeHostKey(String foo, String type){ + removeHostKey(foo, type, null); + } + public void removeHostKey(String foo, String type, byte[] key){ + if(known_hosts==null) return; + known_hosts.remove(foo, type, key); + } + */ + public void addIdentity(String foo) + { + addIdentity(foo, (String)null); + } + public void addIdentity(String foo, String bar) + { + Identity identity=new IdentityFile(foo, this); + if(bar!=null) identity.setPassphrase(bar); + identities.Add(identity); + } + internal String getConfig(String foo){ return (String)(config[foo]); } + + private System.Collections.ArrayList proxies; + void setProxy(String hosts, Proxy proxy) + { + String[] patterns=Util.split(hosts, ","); + if(proxies==null){proxies=new System.Collections.ArrayList();} + lock(proxies) + { + for(int i=0; i + /// Summary description for JSchException. + /// + public class JSchAuthCancelException : JSchException + { + public JSchAuthCancelException() : base() + { + // + // TODO: Add constructor logic here + // + } + + public JSchAuthCancelException(string msg) : base (msg) + { + } + } +} diff --git a/SharpSSH/jsch/JSchException.cs b/SharpSSH/jsch/JSchException.cs new file mode 100644 index 00000000..b7aa42bd --- /dev/null +++ b/SharpSSH/jsch/JSchException.cs @@ -0,0 +1,21 @@ +using System; + +namespace Tamir.SharpSsh.jsch +{ + /// + /// Summary description for JSchException. + /// + public class JSchException : java.Exception + { + public JSchException() : base() + { + // + // TODO: Add constructor logic here + // + } + + public JSchException(string msg) : base (msg) + { + } + } +} diff --git a/SharpSSH/jsch/JSchPartialAuthException.cs b/SharpSSH/jsch/JSchPartialAuthException.cs new file mode 100644 index 00000000..7176fd92 --- /dev/null +++ b/SharpSSH/jsch/JSchPartialAuthException.cs @@ -0,0 +1,26 @@ +using System; + +namespace Tamir.SharpSsh.jsch +{ + /// + /// Summary description for JSchException. + /// + public class JSchPartialAuthException : JSchException + { + string methods; + public JSchPartialAuthException() : base() + { + methods = null; + } + + public JSchPartialAuthException(string msg) : base (msg) + { + methods = msg; + } + + public String getMethods() + { + return methods; + } + } +} diff --git a/SharpSSH/jsch/KeyExchange.cs b/SharpSSH/jsch/KeyExchange.cs new file mode 100644 index 00000000..79014bcc --- /dev/null +++ b/SharpSSH/jsch/KeyExchange.cs @@ -0,0 +1,170 @@ +using System; + +namespace Tamir.SharpSsh.jsch +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + public abstract class KeyExchange + { + + internal const int PROPOSAL_KEX_ALGS=0; + internal const int PROPOSAL_SERVER_HOST_KEY_ALGS=1; + internal const int PROPOSAL_ENC_ALGS_CTOS=2; + internal const int PROPOSAL_ENC_ALGS_STOC=3; + internal const int PROPOSAL_MAC_ALGS_CTOS=4; + internal const int PROPOSAL_MAC_ALGS_STOC=5; + internal const int PROPOSAL_COMP_ALGS_CTOS=6; + internal const int PROPOSAL_COMP_ALGS_STOC=7; + internal const int PROPOSAL_LANG_CTOS=8; + internal const int PROPOSAL_LANG_STOC=9; + internal const int PROPOSAL_MAX=10; + + //static String kex_algs="diffie-hellman-group-exchange-sha1"+ + // ",diffie-hellman-group1-sha1"; + + //static String kex="diffie-hellman-group-exchange-sha1"; + internal static String kex="diffie-hellman-group1-sha1"; + internal static String server_host_key="ssh-rsa,ssh-dss"; + internal static String enc_c2s="blowfish-cbc"; + internal static String enc_s2c="blowfish-cbc"; + internal static String mac_c2s="hmac-md5"; // hmac-md5,hmac-sha1,hmac-ripemd160, + // hmac-sha1-96,hmac-md5-96 + internal static String mac_s2c="hmac-md5"; + //static String comp_c2s="none"; // zlib + //static String comp_s2c="none"; + internal static String lang_c2s=""; + internal static String lang_s2c=""; + + public const int STATE_END=0; + + public Tamir.SharpSsh.java.String[] _guess=null; + protected Session session=null; + protected HASH sha=null; + protected byte[] K=null; + protected byte[] H=null; + protected byte[] K_S=null; + + public abstract void init(Session session, + byte[] V_S, byte[] V_C, byte[] I_S, byte[] I_C); + public abstract bool next(Buffer buf); + public abstract String getKeyType(); + public abstract int getState(); + + /* + void dump(byte[] foo){ + for(int i=0; i>4)&0x0f))); + outs.WriteByte(b2a((byte)(iv[j]&0x0f))); + } + Write(outs,cr); + Write(outs,cr); + } + int i=0; + while(i0) + { + len>>=8; + i++; + } + return i; + } + + internal int writeLength(byte[] data, int index, int len) + { + int i=countLength(len)-1; + if(i==0) + { + data[index++]=(byte)len; + return index; + } + data[index++]=(byte)(0x80|i); + int j=index+i; + while(i>0) + { + data[index+i-1]=(byte)(len&0xff); + len>>=8; + i--; + } + return j; + } + + private Random genRandom() + { + if(random==null) + { + try + { + Type t=Type.GetType(jsch.getConfig("random")); + random=(Random)Activator.CreateInstance(t); + } + catch(Exception e){ Console.Error.WriteLine("connect: random "+e); } + } + return random; + } + + private HASH genHash() + { + try + { + Type t=Type.GetType(jsch.getConfig("md5")); + hash=(HASH)Activator.CreateInstance(t); + hash.init(); + } + catch//(Exception e) + { + } + return hash; + } + private Cipher genCipher() + { + try + { + Type t; + t=Type.GetType(jsch.getConfig("3des-cbc")); + cipher=(Cipher)(Activator.CreateInstance(t)); + } + catch//(Exception e) + { + } + return cipher; + } + + /* + hash is MD5 + h(0) <- hash(passphrase, iv); + h(n) <- hash(h(n-1), passphrase, iv); + key <- (h(0),...,h(n))[0,..,key.Length]; + */ + [MethodImpl(MethodImplOptions.Synchronized)] + internal byte[] genKey(byte[] passphrase, byte[] iv) + { + if(cipher==null) cipher=genCipher(); + if(hash==null) hash=genHash(); + + byte[] key=new byte[cipher.getBlockSize()]; + int hsize=hash.getBlockSize(); + byte[] hn=new byte[key.Length/hsize*hsize+ + (key.Length%hsize==0?0:hsize)]; + try + { + byte[] tmp=null; + if(vendor==VENDOR_OPENSSH) + { + for(int index=0; index+hsize<=hn.Length;) + { + if(tmp!=null){ hash.update(tmp, 0, tmp.Length); } + hash.update(passphrase, 0, passphrase.Length); + hash.update(iv, 0, iv.Length); + tmp=hash.digest(); + Array.Copy(tmp, 0, hn, index, tmp.Length); + index+=tmp.Length; + } + Array.Copy(hn, 0, key, 0, key.Length); + } + else if(vendor==VENDOR_FSECURE) + { + for(int index=0; index+hsize<=hn.Length;) + { + if(tmp!=null){ hash.update(tmp, 0, tmp.Length); } + hash.update(passphrase, 0, passphrase.Length); + tmp=hash.digest(); + Array.Copy(tmp, 0, hn, index, tmp.Length); + index+=tmp.Length; + } + Array.Copy(hn, 0, key, 0, key.Length); + } + } + catch(Exception e) + { + Console.WriteLine(e); + } + return key; + } + + public void setPassphrase(String passphrase) + { + if(passphrase==null || passphrase.Length==0) + { + setPassphrase((byte[])null); + } + else + { + setPassphrase(Util.getBytes( passphrase )); + } + } + public void setPassphrase(byte[] passphrase) + { + if(passphrase!=null && passphrase.Length==0) + passphrase=null; + this.passphrase=passphrase; + } + + private bool encrypted=false; + private byte[] data=null; + private byte[] iv=null; + private byte[] publickeyblob=null; + + public bool isEncrypted(){ return encrypted; } + public bool decrypt(String _passphrase) + { + byte[] passphrase= Util.getBytes( _passphrase ); + byte[] foo=decrypt(data, passphrase, iv); + if(parse(foo)) + { + encrypted=false; + } + return !encrypted; + } + + public static KeyPair load(JSch jsch, String prvkey) + { + String pubkey=prvkey+".pub"; +// if(!new File(pubkey).exists()) + if(!File.Exists(pubkey)) + { + pubkey=null; + } + return load(jsch, prvkey, pubkey); + } + public static KeyPair load(JSch jsch, String prvkey, String pubkey) + { + + byte[] iv=new byte[8]; // 8 + bool encrypted=true; + byte[] data=null; + + byte[] publickeyblob=null; + + int type=ERROR; + int vendor=VENDOR_OPENSSH; + + try + { + //File file=new File(prvkey); + FileStream fis=File.OpenRead(prvkey); + byte[] buf=new byte[(int)(fis.Length)]; + int len=fis.Read(buf, 0, buf.Length); + fis.Close(); + + int i=0; + + while(i4 && // FSecure + data[0]==(byte)0x3f && + data[1]==(byte)0x6f && + data[2]==(byte)0xf9 && + data[3]==(byte)0xeb) + { + + Buffer _buf=new Buffer(data); + _buf.getInt(); // 0x3f6ff9be + _buf.getInt(); + byte[]_type=_buf.getString(); + //System.outs.println("type: "+new String(_type)); + byte[] _cipher=_buf.getString(); + String cipher=Util.getString(_cipher); + //System.outs.println("cipher: "+cipher); + if(cipher.Equals("3des-cbc")) + { + _buf.getInt(); + byte[] foo=new byte[data.Length-_buf.getOffSet()]; + _buf.getByte(foo); + data=foo; + encrypted=true; + throw new JSchException("unknown privatekey format: "+prvkey); + } + else if(cipher.Equals("none")) + { + _buf.getInt(); + _buf.getInt(); + + encrypted=false; + + byte[] foo=new byte[data.Length-_buf.getOffSet()]; + _buf.getByte(foo); + data=foo; + } + } + + if(pubkey!=null) + { + try + { + //file=new File(pubkey); + fis=File.OpenRead(pubkey); + buf=new byte[(int)(fis.Length)]; + len=fis.Read(buf, 0, buf.Length); + fis.Close(); + + if(buf.Length>4 && // FSecure's public key + buf[0]=='-' && buf[1]=='-' && buf[2]=='-' && buf[3]=='-') + { + + bool valid=true; + i=0; + do{i++;}while(buf.Length>i && buf[i]!=0x0a); + if(buf.Length<=i) {valid=false;} + + while(valid) + { + if(buf[i]==0x0a) + { + bool inheader=false; + for(int j=i+1; j0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + + if(plain[index]!=0x02)return false; + index++; // INTEGER + Length=plain[index++]&0xff; + if((Length&0x80)!=0) + { + int foo=Length&0x7f; Length=0; + while(foo-->0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + index+=Length; + + index++; + Length=plain[index++]&0xff; + if((Length&0x80)!=0) + { + int foo=Length&0x7f; Length=0; + while(foo-->0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + P_array=new byte[Length]; + Array.Copy(plain, index, P_array, 0, Length); + index+=Length; + + index++; + Length=plain[index++]&0xff; + if((Length&0x80)!=0) + { + int foo=Length&0x7f; Length=0; + while(foo-->0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + Q_array=new byte[Length]; + Array.Copy(plain, index, Q_array, 0, Length); + index+=Length; + + index++; + Length=plain[index++]&0xff; + if((Length&0x80)!=0) + { + int foo=Length&0x7f; Length=0; + while(foo-->0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + G_array=new byte[Length]; + Array.Copy(plain, index, G_array, 0, Length); + index+=Length; + + index++; + Length=plain[index++]&0xff; + if((Length&0x80)!=0) + { + int foo=Length&0x7f; Length=0; + while(foo-->0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + pub_array=new byte[Length]; + Array.Copy(plain, index, pub_array, 0, Length); + index+=Length; + + index++; + Length=plain[index++]&0xff; + if((Length&0x80)!=0) + { + int foo=Length&0x7f; Length=0; + while(foo-->0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + prv_array=new byte[Length]; + Array.Copy(plain, index, prv_array, 0, Length); + index+=Length; + } + catch//(Exception e) + { + //System.out.println(e); + //e.printStackTrace(); + return false; + } + return true; + } + + public override byte[] getPublicKeyBlob() + { + byte[] foo=base.getPublicKeyBlob(); + if(foo!=null) return foo; + + if(P_array==null) return null; + + Buffer buf=new Buffer(sshdss.Length+4+ + P_array.Length+4+ + Q_array.Length+4+ + G_array.Length+4+ + pub_array.Length+4); + buf.putString(sshdss); + buf.putString(P_array); + buf.putString(Q_array); + buf.putString(G_array); + buf.putString(pub_array); + return buf.buffer; + } + + private static byte[] sshdss= Util.getBytes( "ssh-dss" ); + internal override byte[] getKeyTypeName(){return sshdss;} + public override int getKeyType(){return DSA;} + + public override int getKeySize(){return key_size; } + public override void dispose() + { + base.dispose(); + P_array=null; + Q_array=null; + G_array=null; + pub_array=null; + prv_array=null; + } + } + +} diff --git a/SharpSSH/jsch/KeyPairGenDSA.cs b/SharpSSH/jsch/KeyPairGenDSA.cs new file mode 100644 index 00000000..ecdc0f1c --- /dev/null +++ b/SharpSSH/jsch/KeyPairGenDSA.cs @@ -0,0 +1,44 @@ +using System; + +namespace Tamir.SharpSsh.jsch +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + public interface KeyPairGenDSA + { + void init(int key_size); + byte[] getX(); + byte[] getY(); + byte[] getP(); + byte[] getQ(); + byte[] getG(); + } + +} diff --git a/SharpSSH/jsch/KeyPairGenRSA.cs b/SharpSSH/jsch/KeyPairGenRSA.cs new file mode 100644 index 00000000..aa739cd7 --- /dev/null +++ b/SharpSSH/jsch/KeyPairGenRSA.cs @@ -0,0 +1,48 @@ +using System; + +namespace Tamir.SharpSsh.jsch +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + public interface KeyPairGenRSA + { + void init(int key_size); + byte[] getD(); + byte[] getE(); + byte[] getN(); + + byte[] getC(); + byte[] getEP(); + byte[] getEQ(); + byte[] getP(); + byte[] getQ(); + } + +} diff --git a/SharpSSH/jsch/KeyPairRSA.cs b/SharpSSH/jsch/KeyPairRSA.cs new file mode 100644 index 00000000..32046634 --- /dev/null +++ b/SharpSSH/jsch/KeyPairRSA.cs @@ -0,0 +1,352 @@ +using System; + +namespace Tamir.SharpSsh.jsch +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + public class KeyPairRSA : KeyPair + { + private byte[] prv_array; + private byte[] pub_array; + private byte[] n_array; + + private byte[] p_array; // prime p + private byte[] q_array; // prime q + private byte[] ep_array; // prime exponent p + private byte[] eq_array; // prime exponent q + private byte[] c_array; // coefficient + + //private int key_size=0; + private int key_size=1024; + + public KeyPairRSA(JSch jsch):base(jsch) + { + } + + internal override void generate(int key_size) + { + this.key_size=key_size; + try + { + Type t=Type.GetType(jsch.getConfig("keypairgen.rsa")); + KeyPairGenRSA keypairgen=(KeyPairGenRSA)(Activator.CreateInstance(t)); + keypairgen.init(key_size); + pub_array=keypairgen.getE(); + prv_array=keypairgen.getD(); + n_array=keypairgen.getN(); + + p_array=keypairgen.getP(); + q_array=keypairgen.getQ(); + ep_array=keypairgen.getEP(); + eq_array=keypairgen.getEQ(); + c_array=keypairgen.getC(); + + keypairgen=null; + } + catch(Exception e) + { + Console.WriteLine("KeyPairRSA: "+e); + throw new JSchException(e.ToString()); + } + } + + private static byte[] begin= Util.getBytes( "-----BEGIN RSA PRIVATE KEY-----"); + private static byte[] end=Util.getBytes("-----END RSA PRIVATE KEY-----"); + + internal override byte[] getBegin(){ return begin; } + internal override byte[] getEnd(){ return end; } + + internal override byte[] getPrivateKey() + { + int content= + 1+countLength(1) + 1 + // INTEGER + 1+countLength(n_array.Length) + n_array.Length + // INTEGER N + 1+countLength(pub_array.Length) + pub_array.Length + // INTEGER pub + 1+countLength(prv_array.Length) + prv_array.Length+ // INTEGER prv + 1+countLength(p_array.Length) + p_array.Length+ // INTEGER p + 1+countLength(q_array.Length) + q_array.Length+ // INTEGER q + 1+countLength(ep_array.Length) + ep_array.Length+ // INTEGER ep + 1+countLength(eq_array.Length) + eq_array.Length+ // INTEGER eq + 1+countLength(c_array.Length) + c_array.Length; // INTEGER c + + int total= + 1+countLength(content)+content; // SEQUENCE + + byte[] plain=new byte[total]; + int index=0; + index=writeSEQUENCE(plain, index, content); + index=writeINTEGER(plain, index, new byte[1]); // 0 + index=writeINTEGER(plain, index, n_array); + index=writeINTEGER(plain, index, pub_array); + index=writeINTEGER(plain, index, prv_array); + index=writeINTEGER(plain, index, p_array); + index=writeINTEGER(plain, index, q_array); + index=writeINTEGER(plain, index, ep_array); + index=writeINTEGER(plain, index, eq_array); + index=writeINTEGER(plain, index, c_array); + return plain; + } + + internal override bool parse(byte [] plain) + { + /* + byte[] p_array; + byte[] q_array; + byte[] dmp1_array; + byte[] dmq1_array; + byte[] iqmp_array; + */ + try + { + int index=0; + int Length=0; + + if(vendor==VENDOR_FSECURE) + { + if(plain[index]!=0x30) + { // FSecure + Buffer buf=new Buffer(plain); + pub_array=buf.getMPIntBits(); + prv_array=buf.getMPIntBits(); + n_array=buf.getMPIntBits(); + byte[] u_array=buf.getMPIntBits(); + p_array=buf.getMPIntBits(); + q_array=buf.getMPIntBits(); + return true; + } + return false; + } + + index++; // SEQUENCE + Length=plain[index++]&0xff; + if((Length&0x80)!=0) + { + int foo=Length&0x7f; Length=0; + while(foo-->0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + + if(plain[index]!=0x02)return false; + index++; // INTEGER + Length=plain[index++]&0xff; + if((Length&0x80)!=0) + { + int foo=Length&0x7f; Length=0; + while(foo-->0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + index+=Length; + + //System.out.println("int: len="+Length); + //System.out.print(Integer.toHexString(plain[index-1]&0xff)+":"); + //System.out.println(""); + + index++; + Length=plain[index++]&0xff; + if((Length&0x80)!=0) + { + int foo=Length&0x7f; Length=0; + while(foo-->0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + n_array=new byte[Length]; + Array.Copy(plain, index, n_array, 0, Length); + index+=Length; + /* + System.out.println("int: N len="+Length); + for(int i=0; i0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + pub_array=new byte[Length]; + Array.Copy(plain, index, pub_array, 0, Length); + index+=Length; + /* + System.out.println("int: E len="+Length); + for(int i=0; i0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + prv_array=new byte[Length]; + Array.Copy(plain, index, prv_array, 0, Length); + index+=Length; + /* + System.out.println("int: prv len="+Length); + for(int i=0; i0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + p_array=new byte[Length]; + Array.Copy(plain, index, p_array, 0, Length); + index+=Length; + /* + System.out.println("int: P len="+Length); + for(int i=0; i0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + q_array=new byte[Length]; + Array.Copy(plain, index, q_array, 0, Length); + index+=Length; + /* + System.out.println("int: q len="+Length); + for(int i=0; i0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + ep_array=new byte[Length]; + Array.Copy(plain, index, ep_array, 0, Length); + index+=Length; + /* + System.out.println("int: ep len="+Length); + for(int i=0; i0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + eq_array=new byte[Length]; + Array.Copy(plain, index, eq_array, 0, Length); + index+=Length; + /* + System.out.println("int: eq len="+Length); + for(int i=0; i0){ Length=(Length<<8)+(plain[index++]&0xff); } + } + c_array=new byte[Length]; + Array.Copy(plain, index, c_array, 0, Length); + index+=Length; + /* + System.out.println("int: c len="+Length); + for(int i=0; i=bufl) + { + addInvalidLine(System.Text.Encoding.Default.GetString(buf, 0, bufl)); + goto loop; + } + + sb.Length = 0; + while(j=bufl || host.Length==0) + { + addInvalidLine(System.Text.Encoding.Default.GetString(buf, 0, bufl)); + goto loop; + } + + sb.Length=0; + type=-1; + while(j=bufl) + { + addInvalidLine(Util.getString(buf, 0, bufl)); + goto loop; + } + + sb.Length=0; + while(j>24); +// tmp[1]=(byte)(len>>16); +// tmp[2]=(byte)(len>>8); +// tmp[3]=(byte)(len); +// Array.Copy(tmp, 0, buffer.buffer, 0, 4); +// buffer.buffer[4]=(byte)pad; +// lock(random) +// { +// random.fill(buffer.buffer, buffer.index, pad); +// } +// buffer.skip(pad); +// //buffer.putPad(pad); +// /* +// for(int i=0; i>24); + tmp[1]=(byte)(len>>16); + tmp[2]=(byte)(len>>8); + tmp[3]=(byte)(len); + Array.Copy(tmp, 0, buffer.buffer, 0, 4); + buffer.buffer[4]=(byte)pad; + lock(random) + { + random.fill(buffer.buffer, buffer.index, pad); + } + buffer.skip(pad); + //buffer.putPad(pad); + /* + for(int i=0; i0) + { + socket.setSoTimeout(timeout); + } + socket.setTcpNoDelay(true); + + outs.write(new String("CONNECT "+host+":"+port+" HTTP/1.0\r\n").getBytes()); + + if(user!=null && passwd!=null) + { + byte[] _code=(user+":"+passwd).getBytes(); + _code=Util.toBase64(_code, 0, _code.Length); + outs.write(new String("Proxy-Authorization: Basic ").getBytes()); + outs.write(_code); + outs.write(new String("\r\n").getBytes()); + } + + outs.write(new String("\r\n").getBytes()); + outs.flush(); + + int foo=0; + + StringBuffer sb=new StringBuffer(); + while(foo>=0) + { + foo=ins.read(); if(foo!=13){sb.append((char)foo); continue;} + foo=ins.read(); if(foo!=10){continue;} + break; + } + if(foo<0) + { + throw new System.IO.IOException(); + } + + String response=sb.toString(); + String reason="Unknow reason"; + int code=-1; + try + { + foo=response.indexOf(' '); + int bar=response.indexOf(' ', foo+1); + code=Integer.parseInt(response.substring(foo+1, bar)); + reason=response.substring(bar+1); + } + catch(Exception e) + { + } + if(code!=200) + { + throw new System.IO.IOException("proxy error: "+reason); + } + + /* + while(foo>=0){ + foo=in.read(); if(foo!=13) continue; + foo=in.read(); if(foo!=10) continue; + foo=in.read(); if(foo!=13) continue; + foo=in.read(); if(foo!=10) continue; + break; + } + */ + + int count=0; + while(true) + { + count=0; + while(foo>=0) + { + foo=ins.read(); if(foo!=13){count++; continue;} + foo=ins.read(); if(foo!=10){continue;} + break; + } + if(foo<0) + { + throw new System.IO.IOException(); + } + if(count==0)break; + } + } + catch(RuntimeException e) + { + throw e; + } + catch(Exception e) + { + try{ if(socket!=null)socket.close(); } + catch(Exception eee) + { + } + String message="ProxyHTTP: "+e.toString(); + throw e; + } + } + public Stream getInputStream(){ return ins.s; } + public Stream getOutputStream(){ return outs.s; } + public Socket getSocket(){ return socket; } + public void close() + { + try + { + if(ins!=null)ins.close(); + if(outs!=null)outs.close(); + if(socket!=null)socket.close(); + } + catch(Exception e) + { + } + ins=null; + outs=null; + socket=null; + } + public static int getDefaultPort() + { + return DEFAULTPORT; + } + } +} diff --git a/SharpSSH/jsch/Random.cs b/SharpSSH/jsch/Random.cs new file mode 100644 index 00000000..0df9578a --- /dev/null +++ b/SharpSSH/jsch/Random.cs @@ -0,0 +1,38 @@ +using System; + +namespace Tamir.SharpSsh.jsch +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + public interface Random + { + void fill(byte[] foo, int start, int len); + } +} diff --git a/SharpSSH/jsch/Request.cs b/SharpSSH/jsch/Request.cs new file mode 100644 index 00000000..6a2b345b --- /dev/null +++ b/SharpSSH/jsch/Request.cs @@ -0,0 +1,39 @@ +using System; + +namespace Tamir.SharpSsh.jsch +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + internal interface Request + { + bool waitForReply(); + void request(Session session, Channel channel); + } +} diff --git a/SharpSSH/jsch/RequestExec.cs b/SharpSSH/jsch/RequestExec.cs new file mode 100644 index 00000000..56f56640 --- /dev/null +++ b/SharpSSH/jsch/RequestExec.cs @@ -0,0 +1,62 @@ +using System; +using Str = Tamir.SharpSsh.java.String; + +namespace Tamir.SharpSsh.jsch +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + internal class RequestExec : Request + { + private String command=""; + internal RequestExec(String foo) + { + this.command=foo; + } + public void request(Session session, Channel channel) + { + Packet packet=session.packet; + Buffer buf=session.buf; + // send + // byte SSH_MSG_CHANNEL_REQUEST(98) + // uint32 recipient channel + // string request type // "exec" + // boolean want reply // 0 + // string command + packet.reset(); + buf.putByte((byte) Session.SSH_MSG_CHANNEL_REQUEST); + buf.putInt(channel.getRecipient()); + buf.putString(new Str("exec").getBytes()); + buf.putByte((byte)(waitForReply() ? 1 : 0)); + buf.putString(new Str(command).getBytes()); + session.write(packet); + } + public bool waitForReply(){ return false; } + } +} diff --git a/SharpSSH/jsch/RequestPtyReq.cs b/SharpSSH/jsch/RequestPtyReq.cs new file mode 100644 index 00000000..80d8553c --- /dev/null +++ b/SharpSSH/jsch/RequestPtyReq.cs @@ -0,0 +1,60 @@ +using System; + +namespace Tamir.SharpSsh.jsch +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + class RequestPtyReq : Request + { + void setCode(String cookie) + { + } + public void request(Session session, Channel channel) + { + Buffer buf=new Buffer(); + Packet packet=new Packet(buf); + + packet.reset(); + buf.putByte((byte) Session.SSH_MSG_CHANNEL_REQUEST); + buf.putInt(channel.getRecipient()); + buf.putString(Util.getBytes("pty-req")); + buf.putByte((byte)(waitForReply() ? 1 : 0)); + buf.putString(Util.getBytes("vt100")); + buf.putInt(80); + buf.putInt(24); + buf.putInt(640); + buf.putInt(480); + buf.putString(Util.getBytes("")); + session.write(packet); + } + public bool waitForReply(){ return false; } + } + +} diff --git a/SharpSSH/jsch/RequestSftp.cs b/SharpSSH/jsch/RequestSftp.cs new file mode 100644 index 00000000..0e61ab2b --- /dev/null +++ b/SharpSSH/jsch/RequestSftp.cs @@ -0,0 +1,73 @@ +using System; + +namespace Tamir.SharpSsh.jsch +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + public class RequestSftp : Request + { + public void request(Session session, Channel channel) + { + Buffer buf=new Buffer(); + Packet packet=new Packet(buf); + + bool reply=waitForReply(); + if(reply) + { + channel.reply=-1; + } + + packet.reset(); + buf.putByte((byte)Session.SSH_MSG_CHANNEL_REQUEST); + buf.putInt(channel.getRecipient()); + buf.putString(Util.getBytes("subsystem")); + buf.putByte((byte)(waitForReply() ? 1 : 0)); + buf.putString(Util.getBytes("sftp")); + session.write(packet); + + if(reply) + { + while(channel.reply==-1) + { + try{System.Threading.Thread.Sleep(10);} + catch//(Exception ee) + { + } + } + if(channel.reply==0) + { + throw new JSchException("failed to send sftp request"); + } + } + } + public bool waitForReply(){ return true; } + } + +} diff --git a/SharpSSH/jsch/RequestShell.cs b/SharpSSH/jsch/RequestShell.cs new file mode 100644 index 00000000..9df51494 --- /dev/null +++ b/SharpSSH/jsch/RequestShell.cs @@ -0,0 +1,56 @@ +using System; + +namespace Tamir.SharpSsh.jsch +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + class RequestShell : Request + { + public void request(Session session, Channel channel) + { + Buffer buf=new Buffer(); + Packet packet=new Packet(buf); + + // send + // byte SSH_MSG_CHANNEL_REQUEST(98) + // uint32 recipient channel + // string request type // "shell" + // boolean want reply // 0 + packet.reset(); + buf.putByte((byte) Session.SSH_MSG_CHANNEL_REQUEST); + buf.putInt(channel.getRecipient()); + buf.putString(Util.getBytes("shell")); + buf.putByte((byte)(waitForReply() ? 1 : 0)); + session.write(packet); + } + public bool waitForReply(){ return false; } + } + +} diff --git a/SharpSSH/jsch/RequestSignal.cs b/SharpSSH/jsch/RequestSignal.cs new file mode 100644 index 00000000..321381d8 --- /dev/null +++ b/SharpSSH/jsch/RequestSignal.cs @@ -0,0 +1,54 @@ +using System; + +namespace Tamir.SharpSsh.jsch +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + class RequestSignal : Request + { + String signal="KILL"; + public void setSignal(String foo){ signal=foo; } + public void request(Session session, Channel channel) + { + Buffer buf=new Buffer(); + Packet packet=new Packet(buf); + + packet.reset(); + buf.putByte((byte) Session.SSH_MSG_CHANNEL_REQUEST); + buf.putInt(channel.getRecipient()); + buf.putString( Util.getBytes("signal")); + buf.putByte((byte)(waitForReply() ? 1 : 0)); + buf.putString(Util.getBytes(signal)); + session.write(packet); + } + public bool waitForReply(){ return false; } + } + +} diff --git a/SharpSSH/jsch/RequestSubsystem.cs b/SharpSSH/jsch/RequestSubsystem.cs new file mode 100644 index 00000000..d8ae0e90 --- /dev/null +++ b/SharpSSH/jsch/RequestSubsystem.cs @@ -0,0 +1,83 @@ + +using Tamir.SharpSsh.java; +using Tamir.SharpSsh.java.lang; + +namespace Tamir.SharpSsh.jsch +{ + /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ + /* + Copyright (c) 2005 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + public class RequestSubsystem : Request + { + private bool want_reply=true; + private String subsystem=null; + public void request(Session session, Channel channel, String subsystem, bool want_reply) + { + this.subsystem=subsystem; + this.want_reply=want_reply; + this.request(session, channel); + } + public void request(Session session, Channel channel) + { + Buffer buf=new Buffer(); + Packet packet=new Packet(buf); + + bool reply=waitForReply(); + if(reply) + { + channel.reply=-1; + } + + packet.reset(); + buf.putByte((byte)Session.SSH_MSG_CHANNEL_REQUEST); + buf.putInt(channel.getRecipient()); + buf.putString( new String( "subsystem" ).getBytes()); + buf.putByte((byte)(waitForReply() ? 1 : 0)); + buf.putString(subsystem.getBytes()); + session.write(packet); + + if(reply) + { + while(channel.reply==-1) + { + try{Thread.sleep(10);} + catch//(System.Exception ee) + { + } + } + if(channel.reply==0) + { + throw new JSchException("failed to send subsystem request"); + } + } + } + public bool waitForReply(){ return want_reply; } + } + +} diff --git a/SharpSSH/jsch/RequestWindowChange.cs b/SharpSSH/jsch/RequestWindowChange.cs new file mode 100644 index 00000000..491902e5 --- /dev/null +++ b/SharpSSH/jsch/RequestWindowChange.cs @@ -0,0 +1,74 @@ +using System; + +namespace Tamir.SharpSsh.jsch +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + public class RequestWindowChange : Request + { + internal int width_columns=80; + internal int height_rows=24; + internal int width_pixels=640; + internal int height_pixels=480; + public void setSize(int row, int col, int wp, int hp) + { + this.width_columns=row; + this.height_rows=col; + this.width_pixels=wp; + this.height_pixels=hp; + } + public void request(Session session, Channel channel) + { + Buffer buf=new Buffer(); + Packet packet=new Packet(buf); + + //byte SSH_MSG_CHANNEL_REQUEST + //uint32 recipient_channel + //string "window-change" + //boolean FALSE + //uint32 terminal width, columns + //uint32 terminal height, rows + //uint32 terminal width, pixels + //uint32 terminal height, pixels + packet.reset(); + buf.putByte((byte) Session.SSH_MSG_CHANNEL_REQUEST); + buf.putInt(channel.getRecipient()); + buf.putString(Util.getBytes("window-change")); + buf.putByte((byte)(waitForReply() ? 1 : 0)); + buf.putInt(width_columns); + buf.putInt(height_rows); + buf.putInt(width_pixels); + buf.putInt(height_pixels); + session.write(packet); + } + public bool waitForReply(){ return false; } + } + +} diff --git a/SharpSSH/jsch/RequestX11.cs b/SharpSSH/jsch/RequestX11.cs new file mode 100644 index 00000000..23c186e2 --- /dev/null +++ b/SharpSSH/jsch/RequestX11.cs @@ -0,0 +1,67 @@ +using System; + +namespace Tamir.SharpSsh.jsch +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + class RequestX11 : Request + { + public void setCookie(String cookie) + { + ChannelX11.cookie=Util.getBytes(cookie); + } + public void request(Session session, Channel channel) + { + Buffer buf=new Buffer(); + Packet packet=new Packet(buf); + + // byte SSH_MSG_CHANNEL_REQUEST(98) + // uint32 recipient channel + // string request type // "x11-req" + // boolean want reply // 0 + // boolean single connection + // string x11 authentication protocol // "MIT-MAGIC-COOKIE-1". + // string x11 authentication cookie + // uint32 x11 screen number + packet.reset(); + buf.putByte((byte) Session.SSH_MSG_CHANNEL_REQUEST); + buf.putInt(channel.getRecipient()); + buf.putString(Util.getBytes("x11-req")); + buf.putByte((byte)(waitForReply() ? 1 : 0)); + buf.putByte((byte)0); + buf.putString(Util.getBytes("MIT-MAGIC-COOKIE-1")); + buf.putString(ChannelX11.getFakedCookie(session)); + buf.putInt(0); + session.write(packet); + } + public bool waitForReply(){ return false; } + } + +} diff --git a/SharpSSH/jsch/ServerSocketFactory.cs b/SharpSSH/jsch/ServerSocketFactory.cs new file mode 100644 index 00000000..efd9602c --- /dev/null +++ b/SharpSSH/jsch/ServerSocketFactory.cs @@ -0,0 +1,13 @@ +using System; +using Tamir.SharpSsh.java.net; + +namespace Tamir.SharpSsh.jsch +{ + /// + /// Summary description for ServerSocketFactory. + /// + public interface ServerSocketFactory + { + ServerSocket createServerSocket(int port, int backlog, InetAddress bindAddr); + } +} diff --git a/SharpSSH/jsch/Session.cs b/SharpSSH/jsch/Session.cs new file mode 100644 index 00000000..4e046419 --- /dev/null +++ b/SharpSSH/jsch/Session.cs @@ -0,0 +1,1792 @@ +//using System; +using System.IO; +using System.Runtime.CompilerServices; +using Tamir.SharpSsh.java; +using Tamir.SharpSsh.java.util; +using Tamir.SharpSsh.java.net; +using Tamir.SharpSsh.java.lang; +using Exception = System.Exception; +using NullReferenceException = System.NullReferenceException; +using ThreadInterruptedException = System.Threading.ThreadInterruptedException; + +namespace Tamir.SharpSsh.jsch +{ + + + public class Session : Runnable + { + private static String version="SharpSSH-"+Tamir.SharpSsh.SshBase.Version.ToString()+"-JSCH-0.1.28"; + + // http://ietf.org/internet-drafts/draft-ietf-secsh-assignednumbers-01.txt + internal const int SSH_MSG_DISCONNECT= 1; + internal const int SSH_MSG_IGNORE= 2; + internal const int SSH_MSG_UNIMPLEMENTED= 3; + internal const int SSH_MSG_DEBUG= 4; + internal const int SSH_MSG_SERVICE_REQUEST= 5; + internal const int SSH_MSG_SERVICE_ACCEPT= 6; + internal const int SSH_MSG_KEXINIT= 20; + internal const int SSH_MSG_NEWKEYS= 21; + internal const int SSH_MSG_KEXDH_INIT= 30; + internal const int SSH_MSG_KEXDH_REPLY= 31; + internal const int SSH_MSG_KEX_DH_GEX_GROUP= 31; + internal const int SSH_MSG_KEX_DH_GEX_INIT= 32; + internal const int SSH_MSG_KEX_DH_GEX_REPLY= 33; + internal const int SSH_MSG_KEX_DH_GEX_REQUEST= 34; + internal const int SSH_MSG_USERAUTH_REQUEST= 50; + internal const int SSH_MSG_USERAUTH_FAILURE= 51; + internal const int SSH_MSG_USERAUTH_SUCCESS= 52; + internal const int SSH_MSG_USERAUTH_BANNER= 53; + internal const int SSH_MSG_USERAUTH_INFO_REQUEST= 60; + internal const int SSH_MSG_USERAUTH_INFO_RESPONSE= 61; + internal const int SSH_MSG_USERAUTH_PK_OK= 60; + internal const int SSH_MSG_GLOBAL_REQUEST= 80; + internal const int SSH_MSG_REQUEST_SUCCESS= 81; + internal const int SSH_MSG_REQUEST_FAILURE= 82; + internal const int SSH_MSG_CHANNEL_OPEN= 90; + internal const int SSH_MSG_CHANNEL_OPEN_CONFIRMATION= 91; + internal const int SSH_MSG_CHANNEL_OPEN_FAILURE= 92; + internal const int SSH_MSG_CHANNEL_WINDOW_ADJUST= 93; + internal const int SSH_MSG_CHANNEL_DATA= 94; + internal const int SSH_MSG_CHANNEL_EXTENDED_DATA= 95; + internal const int SSH_MSG_CHANNEL_EOF= 96; + internal const int SSH_MSG_CHANNEL_CLOSE= 97; + internal const int SSH_MSG_CHANNEL_REQUEST= 98; + internal const int SSH_MSG_CHANNEL_SUCCESS= 99; + internal const int SSH_MSG_CHANNEL_FAILURE= 100; + + private byte[] V_S; // server version + private byte[] V_C=("SSH-2.0-"+version).getBytes(); // client version + + private byte[] I_C; // the payload of the client's SSH_MSG_KEXINIT + private byte[] I_S; // the payload of the server's SSH_MSG_KEXINIT + private byte[] K_S; // the host key + + private byte[] session_id; + + private byte[] IVc2s; + private byte[] IVs2c; + private byte[] Ec2s; + private byte[] Es2c; + private byte[] MACc2s; + private byte[] MACs2c; + + private int seqi=0; + private int seqo=0; + + private Cipher s2ccipher; + private Cipher c2scipher; + private MAC s2cmac; + private MAC c2smac; + private byte[] mac_buf; + + private Compression deflater; + private Compression inflater; + + private IO io; + private Socket socket; + private int timeout=0; + + private bool _isConnected=false; + + private bool isAuthed=false; + + private Thread connectThread=null; + + internal bool x11_forwarding=false; + + internal Stream In=null; + internal Stream Out=null; + + internal static Random random; + + internal Buffer buf; + internal Packet packet; + + internal SocketFactory socket_factory=null; + + private Hashtable config=null; + + private Proxy proxy=null; + private UserInfo userinfo; + + internal String host="127.0.0.1"; + internal int port=22; + + internal String username=null; + internal String password=null; + + internal JSch jsch; + + internal Session(JSch jsch) + { + ; + this.jsch=jsch; + buf=new Buffer(); + packet=new Packet(buf); + } + + public void connect() + { + connect(timeout); + } + + public void connect(int connectTimeout) + { + if(_isConnected) + { + throw new JSchException("session is already connected"); + } + io=new IO(); + if(random==null) + { + try + { + Class c=Class.forName(getConfig("random")); + random=(Random)(c.newInstance()); + } + catch(Exception e) + { + System.Console.Error.WriteLine("connect: random "+e); + } + } + Packet.setRandom(random); + + try + { + int i, j; + int pad=0; + + if(proxy==null) + { + proxy=jsch.getProxy(host); + if(proxy!=null) + { + lock(proxy) + { + proxy.close(); + } + } + } + + if(proxy==null) + { + Stream In; + Stream Out; + if(socket_factory==null) + { + socket=Util.createSocket(host, port, connectTimeout); + In=socket.getInputStream(); + Out=socket.getOutputStream(); + } + else + { + socket=socket_factory.createSocket(host, port); + In=socket_factory.getInputStream(socket); + Out=socket_factory.getOutputStream(socket); + } + //if(timeout>0){ socket.setSoTimeout(timeout); } + socket.setTcpNoDelay(true); + io.setInputStream(In); + io.setOutputStream(Out); + } + else + { + lock(proxy) + { + proxy.connect(socket_factory, host, port, connectTimeout); + io.setInputStream(proxy.getInputStream()); + io.setOutputStream(proxy.getOutputStream()); + socket=proxy.getSocket(); + } + } + + if(connectTimeout>0 && socket!=null) + { + socket.setSoTimeout(connectTimeout); + } + + _isConnected=true; + + while(true) + { + + i=0; + j=0; + while(i4 && (i!=buf.buffer.Length) && + (buf.buffer[0]!='S'||buf.buffer[1]!='S'|| + buf.buffer[2]!='H'||buf.buffer[3]!='-')) + { + //System.err.println(new String(buf.buffer, 0, i); + continue; + } + + if(i==buf.buffer.Length || + i<7 || // SSH-1.99 or SSH-2.0 + (buf.buffer[4]=='1' && buf.buffer[6]!='9') // SSH-1.5 + ) + { + throw new JSchException("invalid server's version String"); + } + break; + } + + V_S=new byte[i]; Tamir.SharpSsh.java.System.arraycopy(buf.buffer, 0, V_S, 0, i); + //System.Console.WriteLine("V_S: ("+i+") ["+new String(V_S)+"]"); + + //io.put(V_C, 0, V_C.Length); io.put("\n".getBytes(), 0, 1); + { + // Some Cisco devices will miss to read '\n' if it is sent separately. + byte[] foo=new byte[V_C.Length+1]; + Tamir.SharpSsh.java.System.arraycopy(V_C, 0, foo, 0, V_C.Length); + foo[foo.Length-1]=(byte)'\n'; + io.put(foo, 0, foo.Length); + } + + buf=read(buf); + //System.Console.WriteLine("read: 20 ? "+buf.buffer[5]); + if(buf.buffer[5]!=SSH_MSG_KEXINIT) + { + throw new JSchException("invalid protocol: "+buf.buffer[5]); + } + KeyExchange kex=receive_kexinit(buf); + + while(true) + { + buf=read(buf); + if(kex.getState()==buf.buffer[5]) + { + bool result=kex.next(buf); + if(!result) + { + //System.Console.WriteLine("verify: "+result); + in_kex=false; + throw new JSchException("verify: "+result); + } + } + else + { + in_kex=false; + throw new JSchException("invalid protocol(kex): "+buf.buffer[5]); + } + if(kex.getState()==KeyExchange.STATE_END) + { + break; + } + } + + try{ checkHost(host, kex); } + catch(JSchException ee) + { + in_kex=false; + throw ee; + } + + send_newkeys(); + + // receive SSH_MSG_NEWKEYS(21) + buf=read(buf); + //System.Console.WriteLine("read: 21 ? "+buf.buffer[5]); + if(buf.buffer[5]==SSH_MSG_NEWKEYS) + { + receive_newkeys(buf, kex); + } + else + { + in_kex=false; + throw new JSchException("invalid protocol(newkyes): "+buf.buffer[5]); + } + + bool auth=false; + bool auth_cancel=false; + + UserAuthNone usn=new UserAuthNone(userinfo); + auth=usn.start(this); + + String methods=null; + if(!auth) + { + methods=usn.getMethods(); + if(methods!=null) + { + methods=methods.toLowerCase(); + } + else + { + // methods: publickey,password,keyboard-interactive + methods="publickey,password,keyboard-interactive"; + } + } + + loop: + while(true) + { + + //System.Console.WriteLine("methods: "+methods); + + while(!auth && + methods!=null && methods.Length()>0) + { + + //System.Console.WriteLine(" methods: "+methods); + + UserAuth us=null; + if(methods.startsWith("publickey")) + { + //System.Console.WriteLine(" jsch.identities.size()="+jsch.identities.size()); + lock(jsch.identities) + { + if(jsch.identities.size()>0) + { + us=new UserAuthPublicKey(userinfo); + } + } + } + else if(methods.startsWith("keyboard-interactive")) + { + if(userinfo is UIKeyboardInteractive) + { + us=new UserAuthKeyboardInteractive(userinfo); + } + } + else if(methods.startsWith("password")) + { + us=new UserAuthPassword(userinfo); + } + if(us!=null) + { + try + { + auth=us.start(this); + auth_cancel=false; + } + catch(JSchAuthCancelException ee) + { + //System.Console.WriteLine(ee); + auth_cancel=true; + } + catch(JSchPartialAuthException ee) + { + methods=ee.getMethods(); + //System.Console.WriteLine("PartialAuth: "+methods); + auth_cancel=false; + continue;//loop; + } + catch(RuntimeException ee) + { + throw ee; + } + catch(Exception ee) + { + System.Console.WriteLine("ee: "+ee); // SSH_MSG_DISCONNECT: 2 Too many authentication failures + } + } + if(!auth) + { + int comma=methods.indexOf(","); + if(comma==-1) break; + methods=methods.subString(comma+1); + } + } + break; + } + + if(connectTimeout>0 || timeout>0) + { + socket.setSoTimeout(timeout); + } + + if(auth) + { + isAuthed=true; + connectThread=new Thread(this); + connectThread.setName("Connect thread "+host+" session"); + connectThread.start(); + return; + } + if(auth_cancel) + throw new JSchException("Auth cancel"); + throw new JSchException("Auth fail"); + } + catch(Exception e) + { + in_kex=false; + if(_isConnected) + { + try + { + packet.reset(); + buf.putByte((byte)SSH_MSG_DISCONNECT); + buf.putInt(3); + buf.putString(new String(e.ToString()).getBytes()); + buf.putString(new String("en").getBytes()); + write(packet); + disconnect(); + } + catch(Exception ee) + { + } + } + _isConnected=false; + //e.printStackTrace(); + if(e is RuntimeException) throw (RuntimeException)e; + if(e is JSchException) throw (JSchException)e; + throw new JSchException("Session.connect: "+e); + } + } + + private KeyExchange receive_kexinit(Buffer buf) + { + int j=buf.getInt(); + if(j!=buf.getLength()) + { // packet was compressed and + buf.getByte(); // j is the size of deflated packet. + I_S=new byte[buf.index-5]; + } + else + { + I_S=new byte[j-1-buf.getByte()]; + } + Tamir.SharpSsh.java.System.arraycopy(buf.buffer, buf.s, I_S, 0, I_S.Length); + /* + try{ + byte[] tmp=new byte[I_S.Length]; + Tamir.SharpSsh.java.System.arraycopy(I_S, 0, tmp, 0, I_S.Length); + Buffer tmpb=new Buffer(tmp); + System.Console.WriteLine("I_S: len="+I_S.Length); + tmpb.setOffSet(17); + System.Console.WriteLine("kex: "+new String(tmpb.getString())); + System.Console.WriteLine("server_host_key: "+new String(tmpb.getString())); + System.Console.WriteLine("cipher.c2s: "+new String(tmpb.getString())); + System.Console.WriteLine("cipher.s2c: "+new String(tmpb.getString())); + System.Console.WriteLine("mac.c2s: "+new String(tmpb.getString())); + System.Console.WriteLine("mac.s2c: "+new String(tmpb.getString())); + System.Console.WriteLine("compression.c2s: "+new String(tmpb.getString())); + System.Console.WriteLine("compression.s2c: "+new String(tmpb.getString())); + System.Console.WriteLine("lang.c2s: "+new String(tmpb.getString())); + System.Console.WriteLine("lang.s2c: "+new String(tmpb.getString())); + System.Console.WriteLine("?: "+(tmpb.getByte()&0xff)); + System.Console.WriteLine("??: "+tmpb.getInt()); + } + catch(Exception e){ + System.Console.WriteLine(e); + } + */ + + send_kexinit(); + String[] guess=KeyExchange.guess(I_S, I_C); + if(guess==null) + { + throw new JSchException("Algorithm negotiation fail"); + } + + if(!isAuthed && + (guess[KeyExchange.PROPOSAL_ENC_ALGS_CTOS].equals("none") || + (guess[KeyExchange.PROPOSAL_ENC_ALGS_STOC].equals("none")))) + { + throw new JSchException("NONE Cipher should not be chosen before authentification is successed."); + } + + KeyExchange kex=null; + try + { + Class c=Class.forName(getConfig(guess[KeyExchange.PROPOSAL_KEX_ALGS])); + kex=(KeyExchange)(c.newInstance()); + } + catch(Exception e){ System.Console.Error.WriteLine("kex: "+e); } + kex._guess=guess; + kex.init(this, V_S, V_C, I_S, I_C); + return kex; + } + + private bool in_kex=false; + public void rekey() + { + send_kexinit(); + } + private void send_kexinit() + { + if(in_kex) return; + in_kex=true; + + // byte SSH_MSG_KEXINIT(20) + // byte[16] cookie (random bytes) + // String kex_algorithms + // String server_host_key_algorithms + // String encryption_algorithms_client_to_server + // String encryption_algorithms_server_to_client + // String mac_algorithms_client_to_server + // String mac_algorithms_server_to_client + // String compression_algorithms_client_to_server + // String compression_algorithms_server_to_client + // String languages_client_to_server + // String languages_server_to_client + packet.reset(); + buf.putByte((byte) SSH_MSG_KEXINIT); + lock(random) + { + random.fill(buf.buffer, buf.index, 16); buf.skip(16); + } + buf.putString(getConfig("kex").getBytes()); + buf.putString(getConfig("server_host_key").getBytes()); + buf.putString(getConfig("cipher.c2s").getBytes()); + buf.putString(getConfig("cipher.s2c").getBytes()); + buf.putString(getConfig("mac.c2s").getBytes()); + buf.putString(getConfig("mac.s2c").getBytes()); + buf.putString(getConfig("compression.c2s").getBytes()); + buf.putString(getConfig("compression.s2c").getBytes()); + buf.putString(getConfig("lang.c2s").getBytes()); + buf.putString(getConfig("lang.s2c").getBytes()); + buf.putByte((byte)0); + buf.putInt(0); + + buf.setOffSet(5); + I_C=new byte[buf.getLength()]; + buf.getByte(I_C); + + write(packet); + } + + private void send_newkeys() + { + // send SSH_MSG_NEWKEYS(21) + packet.reset(); + buf.putByte((byte)SSH_MSG_NEWKEYS); + write(packet); + } + + private void checkHost(String host, KeyExchange kex) + { + String shkc=getConfig("StrictHostKeyChecking"); + + //System.Console.WriteLine("shkc: "+shkc); + + byte[] K_S=kex.getHostKey(); + String key_type=kex.getKeyType(); + String key_fprint=kex.getFingerPrint(); + + hostkey=new HostKey(host, K_S); + + HostKeyRepository hkr=jsch.getHostKeyRepository(); + int i=0; + lock(hkr) + { + i=hkr.check(host, K_S); + } + + bool insert=false; + + if((shkc.equals("ask") || shkc.equals("yes")) && + i==HostKeyRepository.CHANGED) + { + String file=null; + lock(hkr) + { + file=hkr.getKnownHostsRepositoryID(); + } + if(file==null){file="known_hosts";} + String message= + "WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!\n"+ + "IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!\n"+ + "Someone could be eavesdropping on you right now (man-in-the-middle attack)!\n"+ + "It is also possible that the "+key_type+" host key has just been changed.\n"+ + "The fingerprint for the "+key_type+" key sent by the remote host is\n"+ + key_fprint+".\n"+ + "Please contact your system administrator.\n"+ + "Add correct host key in "+file+" to get rid of this message."; + + bool b=false; + + if(userinfo!=null) + { + //userinfo.showMessage(message); + b=userinfo.promptYesNo(message+ + "\nDo you want to delete the old key and insert the new key?"); + } + //throw new JSchException("HostKey has been changed: "+host); + if(!b) + { + throw new JSchException("HostKey has been changed: "+host); + } + else + { + lock(hkr) + { + hkr.remove(host, + (key_type.equals("DSA") ? "ssh-dss" : "ssh-rsa"), + null); + insert=true; + } + } + } + + // bool insert=false; + + if((shkc.equals("ask") || shkc.equals("yes")) && + (i!=HostKeyRepository.OK) && !insert) + { + if(shkc.equals("yes")) + { + throw new JSchException("reject HostKey: "+host); + } + //System.Console.WriteLine("finger-print: "+key_fprint); + if(userinfo!=null) + { + bool foo=userinfo.promptYesNo( + "The authenticity of host '"+host+"' can't be established.\n"+ + key_type+" key fingerprint is "+key_fprint+".\n"+ + "Are you sure you want to continue connecting?" + ); + if(!foo) + { + throw new JSchException("reject HostKey: "+host); + } + insert=true; + } + else + { + if(i==HostKeyRepository.NOT_INCLUDED) + throw new JSchException("UnknownHostKey: "+host+". "+key_type+" key fingerprint is "+key_fprint); + else throw new JSchException("HostKey has been changed: "+host); + } + } + + if(shkc.equals("no") && + HostKeyRepository.NOT_INCLUDED==i) + { + insert=true; + } + + if(insert) + { + lock(hkr) + { + hkr.add(host, K_S, userinfo); + } + } + + } + + //public void start(){ (new Thread(this)).start(); } + + public Channel openChannel(String type) + { + if(!_isConnected) + { + throw new JSchException("session is down"); + } + try + { + Channel channel=Channel.getChannel(type); + addChannel(channel); + channel.init(); + return channel; + } + catch(Exception e) + { + System.Console.WriteLine(e); + } + return null; + } + + // encode will bin invoked in write with synchronization. + public void encode(Packet packet) + { + //System.Console.WriteLine("encode: "+packet.buffer.buffer[5]); + //System.Console.WriteLine(" "+packet.buffer.index); + //if(packet.buffer.buffer[5]==96){ + //Thread.dumpStack(); + //} + if(deflater!=null) + { + packet.buffer.index=deflater.compress(packet.buffer.buffer, + 5, packet.buffer.index); + } + if(c2scipher!=null) + { + packet.padding(c2scipher.getIVSize()); + int pad=packet.buffer.buffer[4]; + lock(random) + { + random.fill(packet.buffer.buffer, packet.buffer.index-pad, pad); + } + } + else + { + packet.padding(8); + } + byte[] mac=null; + if(c2smac!=null) + { + c2smac.update(seqo); + c2smac.update(packet.buffer.buffer, 0, packet.buffer.index); + mac=c2smac.doFinal(); + } + if(c2scipher!=null) + { + byte[] buf=packet.buffer.buffer; + c2scipher.update(buf, 0, packet.buffer.index, buf, 0); + } + if(mac!=null) + { + packet.buffer.putByte(mac); + } + } + + int[] uncompress_len=new int[1]; + + private int cipher_size=8; + public Buffer read(Buffer buf) + { + int j=0; + while(true) + { + buf.reset(); + io.getByte(buf.buffer, buf.index, cipher_size); buf.index+=cipher_size; + if(s2ccipher!=null) + { + s2ccipher.update(buf.buffer, 0, cipher_size, buf.buffer, 0); + } +// j=((buf.buffer[0]<<24)&0xff000000)| +// ((buf.buffer[1]<<16)&0x00ff0000)| +// ((buf.buffer[2]<< 8)&0x0000ff00)| +// ((buf.buffer[3] )&0x000000ff); + j=Util.ToInt32( buf.buffer, 0 ); + j=j-4-cipher_size+8; + if(j<0 || (buf.index+j)>buf.buffer.Length) + { + throw new IOException("invalid data"); + } + if(j>0) + { + io.getByte(buf.buffer, buf.index, j); buf.index+=(j); + if(s2ccipher!=null) + { + s2ccipher.update(buf.buffer, cipher_size, j, buf.buffer, cipher_size); + } + } + + if(s2cmac!=null) + { + s2cmac.update(seqi); + s2cmac.update(buf.buffer, 0, buf.index); + byte[] result=s2cmac.doFinal(); + io.getByte(mac_buf, 0, mac_buf.Length); + if(!Arrays.equals(result, mac_buf)) + { + throw new IOException("MAC Error"); + } + } + seqi++; + + if(inflater!=null) + { + //inflater.uncompress(buf); + int pad=buf.buffer[4]; + uncompress_len[0]=buf.index-5-pad; + byte[] foo=inflater.uncompress(buf.buffer, 5, uncompress_len); + if(foo!=null) + { + buf.buffer=foo; + buf.index=5+uncompress_len[0]; + } + else + { + System.Console.Error.WriteLine("fail in inflater"); + break; + } + } + + int type=buf.buffer[5]&0xff; + //System.Console.WriteLine("read: "+type); + if(type==SSH_MSG_DISCONNECT) + { + buf.rewind(); + buf.getInt();buf.getShort(); + int reason_code=buf.getInt(); + byte[] description=buf.getString(); + byte[] language_tag=buf.getString(); + /* + System.Console.Error.WriteLine("SSH_MSG_DISCONNECT:"+ + " "+reason_code+ + " "+new String(description)+ + " "+new String(language_tag)); + */ + throw new JSchException("SSH_MSG_DISCONNECT:"+ + " "+reason_code+ + " "+new String(description)+ + " "+new String(language_tag)); + //break; + } + else if(type==SSH_MSG_IGNORE) + { + } + else if(type==SSH_MSG_DEBUG) + { + buf.rewind(); + buf.getInt();buf.getShort(); + /* + byte always_display=(byte)buf.getByte(); + byte[] message=buf.getString(); + byte[] language_tag=buf.getString(); + System.Console.Error.WriteLine("SSH_MSG_DEBUG:"+ + " "+new String(message)+ + " "+new String(language_tag)); + */ + } + else if(type==SSH_MSG_CHANNEL_WINDOW_ADJUST) + { + buf.rewind(); + buf.getInt();buf.getShort(); + Channel c=Channel.getChannel(buf.getInt(), this); + if(c==null) + { + } + else + { + c.addRemoteWindowSize(buf.getInt()); + } + } + else + { + break; + } + } + buf.rewind(); + return buf; + } + + internal byte[] getSessionId() + { + return session_id; + } + + private void receive_newkeys(Buffer buf, KeyExchange kex) + { + // send_newkeys(); + updateKeys(kex); + in_kex=false; + } + private void updateKeys(KeyExchange kex) + { + byte[] K=kex.getK(); + byte[] H=kex.getH(); + HASH hash=kex.getHash(); + + String[] guess=kex._guess; + + if(session_id==null) + { + session_id=new byte[H.Length]; + Tamir.SharpSsh.java.System.arraycopy(H, 0, session_id, 0, H.Length); + } + + /* + Initial IV client to server: HASH (K || H || "A" || session_id) + Initial IV server to client: HASH (K || H || "B" || session_id) + Encryption key client to server: HASH (K || H || "C" || session_id) + Encryption key server to client: HASH (K || H || "D" || session_id) + Integrity key client to server: HASH (K || H || "E" || session_id) + Integrity key server to client: HASH (K || H || "F" || session_id) + */ + + buf.reset(); + buf.putMPInt(K); + buf.putByte(H); + buf.putByte((byte)0x41); + buf.putByte(session_id); + hash.update(buf.buffer, 0, buf.index); + IVc2s=hash.digest(); + + int j=buf.index-session_id.Length-1; + + buf.buffer[j]++; + hash.update(buf.buffer, 0, buf.index); + IVs2c=hash.digest(); + + buf.buffer[j]++; + hash.update(buf.buffer, 0, buf.index); + Ec2s=hash.digest(); + + buf.buffer[j]++; + hash.update(buf.buffer, 0, buf.index); + Es2c=hash.digest(); + + buf.buffer[j]++; + hash.update(buf.buffer, 0, buf.index); + MACc2s=hash.digest(); + + buf.buffer[j]++; + hash.update(buf.buffer, 0, buf.index); + MACs2c=hash.digest(); + + try + { + Class c; + + c=Class.forName(getConfig(guess[KeyExchange.PROPOSAL_ENC_ALGS_STOC])); + s2ccipher=(Cipher)(c.newInstance()); + while(s2ccipher.getBlockSize()>Es2c.Length) + { + buf.reset(); + buf.putMPInt(K); + buf.putByte(H); + buf.putByte(Es2c); + hash.update(buf.buffer, 0, buf.index); + byte[] foo=hash.digest(); + byte[] bar=new byte[Es2c.Length+foo.Length]; + Tamir.SharpSsh.java.System.arraycopy(Es2c, 0, bar, 0, Es2c.Length); + Tamir.SharpSsh.java.System.arraycopy(foo, 0, bar, Es2c.Length, foo.Length); + Es2c=bar; + } + s2ccipher.init(Cipher.DECRYPT_MODE, Es2c, IVs2c); + cipher_size=s2ccipher.getIVSize(); + c=Class.forName(getConfig(guess[KeyExchange.PROPOSAL_MAC_ALGS_STOC])); + s2cmac=(MAC)(c.newInstance()); + s2cmac.init(MACs2c); + mac_buf=new byte[s2cmac.getBlockSize()]; + + c=Class.forName(getConfig(guess[KeyExchange.PROPOSAL_ENC_ALGS_CTOS])); + c2scipher=(Cipher)(c.newInstance()); + while(c2scipher.getBlockSize()>Ec2s.Length) + { + buf.reset(); + buf.putMPInt(K); + buf.putByte(H); + buf.putByte(Ec2s); + hash.update(buf.buffer, 0, buf.index); + byte[] foo=hash.digest(); + byte[] bar=new byte[Ec2s.Length+foo.Length]; + Tamir.SharpSsh.java.System.arraycopy(Ec2s, 0, bar, 0, Ec2s.Length); + Tamir.SharpSsh.java.System.arraycopy(foo, 0, bar, Ec2s.Length, foo.Length); + Ec2s=bar; + } + c2scipher.init(Cipher.ENCRYPT_MODE, Ec2s, IVc2s); + + c=Class.forName(getConfig(guess[KeyExchange.PROPOSAL_MAC_ALGS_CTOS])); + c2smac=(MAC)(c.newInstance()); + c2smac.init(MACc2s); + + if(!guess[KeyExchange.PROPOSAL_COMP_ALGS_CTOS].equals("none")) + { + String foo=getConfig(guess[KeyExchange.PROPOSAL_COMP_ALGS_CTOS]); + if(foo!=null) + { + try + { + c=Class.forName(foo); + deflater=(Compression)(c.newInstance()); + int level=6; + try{ level=Integer.parseInt(getConfig("compression_level"));} + catch(Exception ee){ } + deflater.init(Compression.DEFLATER, level); + } + catch(Exception ee) + { + System.Console.Error.WriteLine(foo+" isn't accessible."); + } + } + } + else + { + if(deflater!=null) + { + deflater=null; + } + } + if(!guess[KeyExchange.PROPOSAL_COMP_ALGS_STOC].equals("none")) + { + String foo=getConfig(guess[KeyExchange.PROPOSAL_COMP_ALGS_STOC]); + if(foo!=null) + { + try + { + c=Class.forName(foo); + inflater=(Compression)(c.newInstance()); + inflater.init(Compression.INFLATER, 0); + } + catch(Exception ee) + { + System.Console.Error.WriteLine(foo+" isn't accessible."); + } + } + } + else + { + if(inflater!=null) + { + inflater=null; + } + } + } + catch(Exception e){ System.Console.Error.WriteLine("updatekeys: "+e); } + } + + public void write(Packet packet, Channel c, int length) + { + while(true) + { + if(in_kex) + { + try{Thread.Sleep(10);} + catch(ThreadInterruptedException e){}; + continue; + } + lock(c) + { + if(c.rwsize>=length) + { + c.rwsize-=length; + break; + } + } + if(c._close || !c.isConnected()) + { + throw new IOException("channel is broken"); + } + + bool sendit=false; + int s=0; + byte command=0; + int recipient=-1; + lock(c) + { + if(c.rwsize>0) + { + int len=c.rwsize; + if(len>length) + { + len=length; + } + if(len!=length) + { + s=packet.shift(len, (c2smac!=null ? c2smac.getBlockSize() : 0)); + } + command=packet.buffer.buffer[5]; + recipient=c.getRecipient(); + length-=len; + c.rwsize-=len; + sendit=true; + } + } + if(sendit) + { + _write(packet); + if(length==0) + { + return; + } + packet.unshift(command, recipient, s, length); + lock(c) + { + if(c.rwsize>=length) + { + c.rwsize-=length; + break; + } + } + } + + try{Thread.Sleep(100);} + catch(ThreadInterruptedException e){}; + } + _write(packet); + } + /* + public lockpublic void write(Packet packet) { + encode(packet); + if(io!=null){ + io.put(packet); + seqo++; + } + } + */ + public void write(Packet packet) + { + // System.Console.WriteLine("in_kex="+in_kex+" "+(packet.buffer.buffer[5])); + while(in_kex) + { + byte command=packet.buffer.buffer[5]; + //System.Console.WriteLine("command: "+command); + if(command==SSH_MSG_KEXINIT || + command==SSH_MSG_NEWKEYS || + command==SSH_MSG_KEXDH_INIT || + command==SSH_MSG_KEXDH_REPLY || + command==SSH_MSG_DISCONNECT || + command==SSH_MSG_KEX_DH_GEX_GROUP || + command==SSH_MSG_KEX_DH_GEX_INIT || + command==SSH_MSG_KEX_DH_GEX_REPLY || + command==SSH_MSG_KEX_DH_GEX_REQUEST) + { + break; + } + try{Thread.Sleep(10);} + catch(ThreadInterruptedException e){}; + } + _write(packet); + } + [System.Runtime.CompilerServices.MethodImpl(MethodImplOptions.Synchronized)] + private void _write(Packet packet) + { + encode(packet); + if(io!=null) + { + io.put(packet); + seqo++; + } + } + + Runnable thread; + public void run() + { + thread=this; + + byte[] foo; + Buffer buf=new Buffer(); + Packet packet=new Packet(buf); + int i=0; + Channel channel; + int[] start=new int[1]; + int[] length=new int[1]; + KeyExchange kex=null; + + try + { + while(_isConnected && + thread!=null) + { + buf=read(buf); + int msgType=buf.buffer[5]&0xff; + // if(msgType!=94) + //System.Console.WriteLine("read: 94 ? "+msgType); + + if(kex!=null && kex.getState()==msgType) + { + bool result=kex.next(buf); + if(!result) + { + throw new JSchException("verify: "+result); + } + continue; + } + + switch(msgType) + { + case SSH_MSG_KEXINIT: + //System.Console.WriteLine("KEXINIT"); + kex=receive_kexinit(buf); + break; + + case SSH_MSG_NEWKEYS: + //System.Console.WriteLine("NEWKEYS"); + send_newkeys(); + receive_newkeys(buf, kex); + kex=null; + break; + + case SSH_MSG_CHANNEL_DATA: + buf.getInt(); + buf.getByte(); + buf.getByte(); + i=buf.getInt(); + channel=Channel.getChannel(i, this); + foo=buf.getString(start, length); + if(channel==null) + { + break; + } + try + { + channel.write(foo, start[0], length[0]); + } + catch(Exception e) + { + //System.Console.WriteLine(e); + try{channel.disconnect();} + catch(Exception ee){} + break; + } + int len=length[0]; + channel.setLocalWindowSize(channel.lwsize-len); + if(channel.lwsize0) + { + attr.extended=new String[count*2]; + for(int i=0; i0) + { + for(int i=0; i0) + { + for(int i=0; i0 + ||(name.Length>0 || instruction.Length>0) + ){ + UIKeyboardInteractive kbi=(UIKeyboardInteractive)userinfo; + if(userinfo!=null){ + response=kbi.promptKeyboardInteractive(dest, + name, + instruction, + prompt, + echo); + } + } + // byte SSH_MSG_USERAUTH_INFO_RESPONSE(61) + // int num-responses + // string response[1] (ISO-10646 UTF-8) + // ... + // string response[num-responses] (ISO-10646 UTF-8) +//if(response!=null) +//System.out.println("response.length="+response.length); +//else +//System.out.println("response is null"); + packet.reset(); + buf.putByte((byte)Session.SSH_MSG_USERAUTH_INFO_RESPONSE); + if(num>0 && + (response==null || // cancel + num!=response.Length)){ + buf.putInt(0); + if(response==null) + cancel=true; + } + else{ + buf.putInt(num); + for(int i=0; i>24); + tmp[1]=(byte)(sidlen>>16); + tmp[2]=(byte)(sidlen>>8); + tmp[3]=(byte)(sidlen); + Array.Copy(sid, 0, tmp, 4, sidlen); + Array.Copy(buf.buffer, 5, tmp, 4+sidlen, buf.index-5); + + byte[] signature=identity.getSignature(session, tmp); + if(signature==null) + { // for example, too long key length. + break; + } + buf.putString(signature); + + session.write(packet); + + loop2: + while(true) + { + // receive + // byte SSH_MSG_USERAUTH_SUCCESS(52) + // string service name + buf=session.read(buf); + //System.out.println("read: 52 ? "+ buf.buffer[5]); + if(buf.buffer[5]==Session.SSH_MSG_USERAUTH_SUCCESS) + { + return true; + } + else if(buf.buffer[5]==Session.SSH_MSG_USERAUTH_BANNER) + { + buf.getInt(); buf.getByte(); buf.getByte(); + byte[] _message=buf.getString(); + byte[] lang=buf.getString(); + String message=null; + try{ message=Util.getStringUTF8(_message); } + catch + {//(java.io.UnsupportedEncodingException e){ + message=Util.getString(_message); + } + if(userinfo!=null) + { + userinfo.showMessage(message); + } + goto loop2; + } + else if(buf.buffer[5]==Session.SSH_MSG_USERAUTH_FAILURE) + { + buf.getInt(); buf.getByte(); buf.getByte(); + byte[] foo=buf.getString(); + int partial_success=buf.getByte(); + //System.out.println(new String(foo)+ + // " partial_success:"+(partial_success!=0)); + if(partial_success!=0) + { + throw new JSchPartialAuthException(Util.getString(foo)); + } + break; + } + //System.out.println("USERAUTH fail ("+buf.buffer[5]+")"); + //throw new JSchException("USERAUTH fail ("+buf.buffer[5]+")"); + break; + } + } + return false; + } + } + +} diff --git a/SharpSSH/jsch/UserInfo.cs b/SharpSSH/jsch/UserInfo.cs new file mode 100644 index 00000000..b3a36519 --- /dev/null +++ b/SharpSSH/jsch/UserInfo.cs @@ -0,0 +1,43 @@ +using System; + +namespace Tamir.SharpSsh.jsch +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + public interface UserInfo + { + String getPassphrase(); + String getPassword(); + bool promptPassword(String message); + bool promptPassphrase(String message); + bool promptYesNo(String message); + void showMessage(String message); + } +} diff --git a/SharpSSH/jsch/Util.cs b/SharpSSH/jsch/Util.cs new file mode 100644 index 00000000..689b54fc --- /dev/null +++ b/SharpSSH/jsch/Util.cs @@ -0,0 +1,580 @@ +using System; +using System.Threading; + +namespace Tamir.SharpSsh.jsch +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + + public class Util + { + + /// + /// Converts a time_t to DateTime + /// + public static DateTime Time_T2DateTime(uint time_t) + { + long win32FileTime = 10000000*(long)time_t + 116444736000000000; + return DateTime.FromFileTimeUtc(win32FileTime).ToLocalTime(); + } + + private static byte[] b64 = System.Text.Encoding.Default.GetBytes( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="); + private static byte val(byte foo) + { + if(foo == '=') return 0; + for(int j=0; j>4)); + if(buf[i+2]==(byte)'='){ j++; break;} + foo[j+1]=(byte)(((val(buf[i+1])&0x0f)<<4)|((val(buf[i+2])&0x3c)>>2)); + if(buf[i+3]==(byte)'='){ j+=2; break;} + foo[j+2]=(byte)(((val(buf[i+2])&0x03)<<6)|(val(buf[i+3])&0x3f)); + j+=3; + } + byte[] bar=new byte[j]; + Array.Copy(foo, 0, bar, 0, j); + return bar; + } + internal static byte[] toBase64(byte[] buf, int start, int length) + { + + byte[] tmp=new byte[length*2]; + int i,j,k; + + int foo=(length/3)*3+start; + i=0; + for(j=start; j>2)&0x3f; + tmp[i++]=b64[k]; + k=(buf[j]&0x03)<<4|(buf[j+1]>>4)&0x0f; + tmp[i++]=b64[k]; + k=(buf[j+1]&0x0f)<<2|(buf[j+2]>>6)&0x03; + tmp[i++]=b64[k]; + k=buf[j+2]&0x3f; + tmp[i++]=b64[k]; + } + + foo=(start+length)-foo; + if(foo==1) + { + k=(buf[j]>>2)&0x3f; + tmp[i++]=b64[k]; + k=((buf[j]&0x03)<<4)&0x3f; + tmp[i++]=b64[k]; + tmp[i++]=(byte)'='; + tmp[i++]=(byte)'='; + } + else if(foo==2) + { + k=(buf[j]>>2)&0x3f; + tmp[i++]=b64[k]; + k=(buf[j]&0x03)<<4|(buf[j+1]>>4)&0x0f; + tmp[i++]=b64[k]; + k=((buf[j+1]&0x0f)<<2)&0x3f; + tmp[i++]=b64[k]; + tmp[i++]=(byte)'='; + } + byte[] bar=new byte[i]; + Array.Copy(tmp, 0, bar, 0, i); + return bar; + + // return sun.misc.BASE64Encoder().encode(buf); + } + + internal static String[] split(String foo, String split) + { + byte[] buf=Util.getBytes(foo); + System.Collections.ArrayList bar=new System.Collections.ArrayList(); + int start=0; + int index; + while(true) + { + index=foo.IndexOf(split, start); + if(index>=0) + { + bar.Add(Util.getString(buf, start, index-start)); + start=index+1; + continue; + } + bar.Add(Util.getString(buf, start, buf.Length-start)); + break; + } + String[] result=new String[bar.Count]; + for(int i=0; i>4)&0xf]); + sb.Append(chars[(bar)&0xf]); + if(i+1>>1. + if ((ooffset < 0) || (toffset < 0) || (toffset > (long)orig.Length - len) || + (ooffset > (long)other.Length - len)) + { + return false; + } + while (len-- > 0) + { + char c1 = ta[to++]; + char c2 = pa[po++]; + if (c1 == c2) + { + continue; + } + if (ignoreCase) + { + // If characters don't match but case may be ignored, + // try converting both characters to uppercase. + // If the results match, then the comparison scan should + // continue. + char u1 = char.ToUpper(c1); + char u2 = char.ToUpper(c2); + if (u1 == u2) + { + continue; + } + // Unfortunately, conversion to uppercase does not work properly + // for the Georgian alphabet, which has strange rules about case + // conversion. So we need to make one last check before + // exiting. + if (char.ToLower(u1) == char.ToLower(u2)) + { + continue; + } + } + return false; + } + return true; + } + + public static uint ToUInt32( byte [] ptr ,int Index) + { + uint ui = 0; + + ui = ( (uint) ptr[ Index++ ] ) << 24; + ui += ( (uint) ptr[ Index++ ] ) << 16; + ui += ( (uint) ptr[ Index++ ] ) << 8; + ui += (uint) ptr[ Index++ ]; + + return ui; + } + + public static int ToInt32( byte [] ptr ,int Index) + { + return (int)ToUInt32( ptr, Index ); + } + + public static ushort ToUInt16( byte [] ptr , int Index) + { + ushort u = 0; + + u = ( ushort ) ptr[ Index++ ]; + u *= 256; + u += ( ushort ) ptr[ Index++ ]; + + return u; + } + public static bool ArrayContains( Object[] arr, Object o) + { + for(int i=0; iivsize) + { + tmp=new byte[ivsize]; + Array.Copy(iv, 0, tmp, 0, tmp.Length); + iv=tmp; + } + if(key.Length>bsize) + { + tmp=new byte[bsize]; + Array.Copy(key, 0, tmp, 0, tmp.Length); + key=tmp; + } + + try + { + // SecretKeySpec keyspec=new SecretKeySpec(key, "AES"); + // cipher=javax.crypto.Cipher.getInstance("AES/CBC/"+pad); + + // cipher.init((mode==ENCRYPT_MODE? + // javax.crypto.Cipher.ENCRYPT_MODE: + // javax.crypto.Cipher.DECRYPT_MODE), + // keyspec, new IvParameterSpec(iv)); + cipher = (mode==ENCRYPT_MODE? + rijndael.CreateEncryptor(key, iv): + rijndael.CreateDecryptor(key, iv)); + } + catch(Exception e) + { + Console.WriteLine(e); + cipher=null; + } + } + public override void update(byte[] foo, int s1, int len, byte[] bar, int s2) + { + //cipher.update(foo, s1, len, bar, s2); + cipher.TransformBlock(foo, s1, len, bar, s2); + } + + public override string ToString() + { + return "aes128-cbc"; + } + } + +} diff --git a/SharpSSH/jsch/jce/BlowfishCBC.cs b/SharpSSH/jsch/jce/BlowfishCBC.cs new file mode 100644 index 00000000..afbe35eb --- /dev/null +++ b/SharpSSH/jsch/jce/BlowfishCBC.cs @@ -0,0 +1,17 @@ +using System; + +namespace Tamir.SharpSsh.jsch.jce +{ + /// + /// Summary description for BlowfishCBC. + /// + public class BlowfishCBC + { + public BlowfishCBC() + { + // + // TODO: Add constructor logic here + // + } + } +} diff --git a/SharpSSH/jsch/jce/DH.cs b/SharpSSH/jsch/jce/DH.cs new file mode 100644 index 00000000..6e65f73b --- /dev/null +++ b/SharpSSH/jsch/jce/DH.cs @@ -0,0 +1,76 @@ +using System; +using Org.Mentalis.Security.Cryptography; //For DiffieHellman usage + + +namespace Tamir.SharpSsh.jsch.jce +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + public class DH : Tamir.SharpSsh.jsch.DH + { + internal byte[] p; + internal byte[] g; + internal byte[] e; // my public key + internal byte[] e_array; + internal byte[] f; // your public key + internal byte[] K; // shared secret key + internal byte[] K_array; + + private DiffieHellman dh; + public void init() + { + } + public byte[] getE() + { + if(e_array==null) + { + + dh = new DiffieHellmanManaged(p , g, 0); + e_array = dh.CreateKeyExchange(); + } + return e_array; + } + public byte[] getK() + { + if(K_array==null) + { + K_array = dh.DecryptKeyExchange( f); + } + return K_array; + } + public void setP(byte[] p){ this.p=p; } + public void setG(byte[] g){ this.g=g; } + public void setF(byte[] f){ this.f=f; } + // void setP(BigInteger p){this.p=p;} + // void setG(BigInteger g){this.g=g;} + // void setF(BigInteger f){this.f=f;} + } + +} diff --git a/SharpSSH/jsch/jce/HMACMD5.cs b/SharpSSH/jsch/jce/HMACMD5.cs new file mode 100644 index 00000000..e60234f5 --- /dev/null +++ b/SharpSSH/jsch/jce/HMACMD5.cs @@ -0,0 +1,88 @@ +using System; + +namespace Tamir.SharpSsh.jsch.jce +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + public class HMACMD5 : MAC + { + private const String name="hmac-md5"; + private const int bsize=16; + private Org.Mentalis.Security.Cryptography.HMAC mentalis_mac; + private System.Security.Cryptography.CryptoStream cs; + //private Mac mac; + public int getBlockSize(){return bsize;} + public void init(byte[] key) + { + if(key.Length>bsize) + { + byte[] tmp=new byte[bsize]; + Array.Copy(key, 0, tmp, 0, bsize); + key=tmp; + } + // SecretKeySpec skey=new SecretKeySpec(key, "HmacMD5"); + // mac=Mac.getInstance("HmacMD5"); + // mac.init(skey); + mentalis_mac = new Org.Mentalis.Security.Cryptography.HMAC(new System.Security.Cryptography.MD5CryptoServiceProvider(), key); + cs = new System.Security.Cryptography.CryptoStream( System.IO.Stream.Null, mentalis_mac, System.Security.Cryptography.CryptoStreamMode.Write); + } + + private byte[] tmp=new byte[4]; + public void update(int i) + { + tmp[0]=(byte)(i>>24); + tmp[1]=(byte)(i>>16); + tmp[2]=(byte)(i>>8); + tmp[3]=(byte)i; + update(tmp, 0, 4); + } + public void update(byte[] foo, int s, int l) + { + //mac.update(foo, s, l); + cs.Write( foo, s, l); + } + public byte[] doFinal() + { + //return mac.doFinal(); + cs.Close(); + byte[] result = mentalis_mac.Hash; + byte[] key = mentalis_mac.Key; + mentalis_mac.Clear(); + init(key); + + return result; + } + public String getName() + { + return name; + } + } + +} diff --git a/SharpSSH/jsch/jce/HMACMD596.cs b/SharpSSH/jsch/jce/HMACMD596.cs new file mode 100644 index 00000000..c0cf26bc --- /dev/null +++ b/SharpSSH/jsch/jce/HMACMD596.cs @@ -0,0 +1,82 @@ +using System; + +namespace Tamir.SharpSsh.jsch.jce +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + public class HMACMD596 : MAC + { + private const String name="hmac-md5-96"; + private const int bsize=12; + private Org.Mentalis.Security.Cryptography.HMAC mentalis_mac; + private System.Security.Cryptography.CryptoStream cs; + //private Mac mac; + public int getBlockSize(){return bsize;} + public void init(byte[] key) + { + if(key.Length>16) + { + byte[] tmp=new byte[16]; + Array.Copy(key, 0, tmp, 0, 16); + key=tmp; + } + // SecretKeySpec skey=new SecretKeySpec(key, "HmacMD5"); + // mac=Mac.getInstance("HmacMD5"); + // mac.init(skey); + mentalis_mac = new Org.Mentalis.Security.Cryptography.HMAC(new System.Security.Cryptography.MD5CryptoServiceProvider(), key); + cs = new System.Security.Cryptography.CryptoStream( System.IO.Stream.Null, mentalis_mac, System.Security.Cryptography.CryptoStreamMode.Write); + } + private byte[] tmp=new byte[4]; + public void update(int i) + { + tmp[0]=(byte)(i>>24); + tmp[1]=(byte)(i>>16); + tmp[2]=(byte)(i>>8); + tmp[3]=(byte)i; + update(tmp, 0, 4); + } + public void update(byte[] foo, int s, int l) + { + //mac.update(foo, s, l); + cs.Write( foo, s, l); + } + private byte[] buf=new byte[12]; + public byte[] doFinal() + { + cs.Close(); + Array.Copy( mentalis_mac.Hash, 0, buf, 0, 12); + return buf; + } + public String getName() + { + return name; + } + } +} diff --git a/SharpSSH/jsch/jce/HMACSHA1.cs b/SharpSSH/jsch/jce/HMACSHA1.cs new file mode 100644 index 00000000..cde94b56 --- /dev/null +++ b/SharpSSH/jsch/jce/HMACSHA1.cs @@ -0,0 +1,88 @@ +using System; + +namespace Tamir.SharpSsh.jsch.jce +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + public class HMACSHA1 : MAC + { + private const String name="hmac-sha1"; + private const int bsize=20; + private Org.Mentalis.Security.Cryptography.HMAC mentalis_mac; + private System.Security.Cryptography.CryptoStream cs; + //private Mac mac; + public int getBlockSize(){return bsize;} + public void init(byte[] key) + { + if(key.Length>bsize) + { + byte[] tmp=new byte[bsize]; + Array.Copy(key, 0, tmp, 0, bsize); + key=tmp; + } + // SecretKeySpec skey=new SecretKeySpec(key, "HmacSHA1"); + // mac=Mac.getInstance("HmacSHA1"); + // mac.init(skey); + mentalis_mac = new Org.Mentalis.Security.Cryptography.HMAC(new System.Security.Cryptography.SHA1CryptoServiceProvider(), key); + cs = new System.Security.Cryptography.CryptoStream( System.IO.Stream.Null, mentalis_mac, System.Security.Cryptography.CryptoStreamMode.Write); + } + private byte[] tmp=new byte[4]; + public void update(int i) + { + tmp[0]=(byte)(i>>24); + tmp[1]=(byte)(i>>16); + tmp[2]=(byte)(i>>8); + tmp[3]=(byte)i; + update(tmp, 0, 4); + } + public void update(byte[] foo, int s, int l) + { + //mac.update(foo, s, l); + cs.Write( foo , s, l); + } + public byte[] doFinal() + { + Console.WriteLine("Sha1"); + //return mac.doFinal(); + cs.Close(); + byte[] result = mentalis_mac.Hash; + byte[] key = mentalis_mac.Key; + mentalis_mac.Clear(); + init(key); + + return result; + } + public String getName() + { + return name; + } + } + +} diff --git a/SharpSSH/jsch/jce/HMACSHA196.cs b/SharpSSH/jsch/jce/HMACSHA196.cs new file mode 100644 index 00000000..d009954c --- /dev/null +++ b/SharpSSH/jsch/jce/HMACSHA196.cs @@ -0,0 +1,85 @@ +using System; + +namespace Tamir.SharpSsh.jsch.jce +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + public class HMACSHA196 : MAC + { + private const String name="hmac-sha1-96"; + private const int bsize=12; + private Org.Mentalis.Security.Cryptography.HMAC mentalis_mac; + private System.Security.Cryptography.CryptoStream cs; + //private Mac mac; + public int getBlockSize(){return bsize;} + public void init(byte[] key) + { + if(key.Length>20) + { + byte[] tmp=new byte[20]; + Array.Copy(key, 0, tmp, 0, 20); + key=tmp; + } + // SecretKeySpec skey=new SecretKeySpec(key, "HmacSHA1"); + // mac=Mac.getInstance("HmacSHA1"); + // mac.init(skey); + mentalis_mac = new Org.Mentalis.Security.Cryptography.HMAC(new System.Security.Cryptography.MD5CryptoServiceProvider(), key); + cs = new System.Security.Cryptography.CryptoStream( System.IO.Stream.Null, mentalis_mac, System.Security.Cryptography.CryptoStreamMode.Write); + } + private byte[] tmp=new byte[4]; + public void update(int i) + { + tmp[0]=(byte)(i>>24); + tmp[1]=(byte)(i>>16); + tmp[2]=(byte)(i>>8); + tmp[3]=(byte)i; + update(tmp, 0, 4); + } + public void update(byte[] foo, int s, int l) + { + //mac.update(foo, s, l); + cs.Write( foo , s, l); + } + private byte[] buf=new byte[12]; + public byte[] doFinal() + { + // System.arraycopy(mac.doFinal(), 0, buf, 0, 12); + // return buf; + cs.Close(); + Array.Copy( mentalis_mac.Hash, 0, buf, 0, 12); + return buf; + } + public String getName() + { + return name; + } + } + +} diff --git a/SharpSSH/jsch/jce/KeyPairGenDSA.cs b/SharpSSH/jsch/jce/KeyPairGenDSA.cs new file mode 100644 index 00000000..1a44f26b --- /dev/null +++ b/SharpSSH/jsch/jce/KeyPairGenDSA.cs @@ -0,0 +1,74 @@ +using System; + +namespace Tamir.SharpSsh.jsch.jce +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + public class KeyPairGenDSA : Tamir.SharpSsh.jsch.KeyPairGenDSA + { + byte[] x; // private + byte[] y; // public + byte[] p; + byte[] q; + byte[] g; + + public void init(int key_size) + { +// KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA"); +// keyGen.initialize(key_size, new SecureRandom()); +// KeyPair pair = keyGen.generateKeyPair(); +// PublicKey pubKey=pair.getPublic(); +// PrivateKey prvKey=pair.getPrivate(); + + System.Security.Cryptography.DSACryptoServiceProvider dsa = new System.Security.Cryptography.DSACryptoServiceProvider(key_size); + System.Security.Cryptography.DSAParameters DSAKeyInfo = dsa.ExportParameters(true); + +// x=((DSAPrivateKey)prvKey).getX().toByteArray(); +// y=((DSAPublicKey)pubKey).getY().toByteArray(); +// +// DSAParams _params=((DSAKey)prvKey).getParams(); +// p=_params.getP().toByteArray(); +// q=_params.getQ().toByteArray(); +// g=_params.getG().toByteArray(); + + x = DSAKeyInfo.X; + y = DSAKeyInfo.Y; + p = DSAKeyInfo.P; + q = DSAKeyInfo.Q; + g = DSAKeyInfo.G; + } + public byte[] getX(){return x;} + public byte[] getY(){return y;} + public byte[] getP(){return p;} + public byte[] getQ(){return q;} + public byte[] getG(){return g;} + } + +} diff --git a/SharpSSH/jsch/jce/KeyPairGenRSA.cs b/SharpSSH/jsch/jce/KeyPairGenRSA.cs new file mode 100644 index 00000000..0fda8a1a --- /dev/null +++ b/SharpSSH/jsch/jce/KeyPairGenRSA.cs @@ -0,0 +1,93 @@ +using System; + +namespace Tamir.SharpSsh.jsch.jce +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + public class KeyPairGenRSA : Tamir.SharpSsh.jsch.KeyPairGenRSA + { + byte[] d; // private + byte[] e; // public + byte[] n; + + byte[] c; // coefficient + byte[] ep; // exponent p + byte[] eq; // exponent q + byte[] p; // prime p + byte[] q; // prime q + + System.Security.Cryptography.RSAParameters RSAKeyInfo; + + public void init(int key_size) + { + // KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); + // keyGen.initialize(key_size, new SecureRandom()); + // KeyPair pair = keyGen.generateKeyPair(); + // + // PublicKey pubKey=pair.getPublic(); + // PrivateKey prvKey=pair.getPrivate(); + + System.Security.Cryptography.RSACryptoServiceProvider rsa = new System.Security.Cryptography.RSACryptoServiceProvider(key_size); + RSAKeyInfo = rsa.ExportParameters(true); + + // d=((RSAPrivateKey)prvKey).getPrivateExponent().toByteArray(); + // e=((RSAPublicKey)pubKey).getPublicExponent().toByteArray(); + // n=((RSAKey)prvKey).getModulus().toByteArray(); + // + // c=((RSAPrivateCrtKey)prvKey).getCrtCoefficient().toByteArray(); + // ep=((RSAPrivateCrtKey)prvKey).getPrimeExponentP().toByteArray(); + // eq=((RSAPrivateCrtKey)prvKey).getPrimeExponentQ().toByteArray(); + // p=((RSAPrivateCrtKey)prvKey).getPrimeP().toByteArray(); + // q=((RSAPrivateCrtKey)prvKey).getPrimeQ().toByteArray(); + + d= RSAKeyInfo.D ; + e=RSAKeyInfo.Exponent ; + n=RSAKeyInfo.Modulus ; + + c=RSAKeyInfo.InverseQ ; + ep=RSAKeyInfo.DP ; + eq=RSAKeyInfo.DQ ; + p=RSAKeyInfo.P ; + q=RSAKeyInfo.Q ; + } + public byte[] getD(){return d;} + public byte[] getE(){return e;} + public byte[] getN(){return n;} + public byte[] getC(){return c;} + public byte[] getEP(){return ep;} + public byte[] getEQ(){return eq;} + public byte[] getP(){return p;} + public byte[] getQ(){return q;} + public System.Security.Cryptography.RSAParameters KeyInfo + { + get{return RSAKeyInfo;} + } + } +} diff --git a/SharpSSH/jsch/jce/MD5.cs b/SharpSSH/jsch/jce/MD5.cs new file mode 100644 index 00000000..4a2a4e4d --- /dev/null +++ b/SharpSSH/jsch/jce/MD5.cs @@ -0,0 +1,69 @@ +using System; + +namespace Tamir.SharpSsh.jsch.jce +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + public class MD5 : Tamir.SharpSsh.jsch.HASH + { + //MessageDigest md; + internal System.Security.Cryptography.MD5CryptoServiceProvider md; + private System.Security.Cryptography.CryptoStream cs; + + public override int getBlockSize(){return 16;} + public override void init() + { + try + { + //md=MessageDigest.getInstance("MD5"); + md = new System.Security.Cryptography.MD5CryptoServiceProvider(); + cs = new System.Security.Cryptography.CryptoStream( System.IO.Stream.Null, md, System.Security.Cryptography.CryptoStreamMode.Write); + } + catch(Exception e) + { + Console.WriteLine(e); + } + } + public override void update(byte[] foo, int start, int len) + { + //md.update(foo, start, len); + cs.Write(foo, start, len); + } + public override byte[] digest() + { + cs.Close(); + byte[] result = md.Hash; + md.Clear();//Reinitiazing hash objects + init(); + + return result; + } + } +} diff --git a/SharpSSH/jsch/jce/Random.cs b/SharpSSH/jsch/jce/Random.cs new file mode 100644 index 00000000..0c717871 --- /dev/null +++ b/SharpSSH/jsch/jce/Random.cs @@ -0,0 +1,75 @@ +using System; + +namespace Tamir.SharpSsh.jsch.jce +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + public class Random : Tamir.SharpSsh.jsch.Random + { + private byte[] tmp=new byte[16]; + //private SecureRandom random; + private System.Security.Cryptography.RNGCryptoServiceProvider rand; + public Random() + { + // random=null; + // random = new SecureRandom(); + // + // try{ random=SecureRandom.getInstance("SHA1PRNG"); } + // catch(java.security.NoSuchAlgorithmException e){ + // // System.out.println(e); + // + // // The following code is for IBM's JCE + // try{ random=SecureRandom.getInstance("IBMSecureRandom"); } + // catch(java.security.NoSuchAlgorithmException ee){ + // System.out.println(ee); + // } + // } + rand = new System.Security.Cryptography.RNGCryptoServiceProvider(); + } + static int times = 0; + public void fill(byte[] foo, int start, int len) + { + try + { + if(len>tmp.Length){ tmp=new byte[len]; } + //random.nextBytes(tmp); + rand.GetBytes(tmp); + Array.Copy(tmp, 0, foo, start, len); + } + catch(Exception e) + { + times++; + Console.WriteLine(times+") Array.Copy(tmp={0}, 0, foo={1}, {2}, {3}", tmp.Length, foo.Length, start, len); + //Console.WriteLine(e.StackTrace); + } + } + } + +} diff --git a/SharpSSH/jsch/jce/SHA1.cs b/SharpSSH/jsch/jce/SHA1.cs new file mode 100644 index 00000000..d3ee84be --- /dev/null +++ b/SharpSSH/jsch/jce/SHA1.cs @@ -0,0 +1,66 @@ +using System; + +namespace Tamir.SharpSsh.jsch.jce +{ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +public class SHA1 : Tamir.SharpSsh.jsch.HASH{ + //MessageDigest md; + internal System.Security.Cryptography.SHA1CryptoServiceProvider md; + private System.Security.Cryptography.CryptoStream cs; + + public override int getBlockSize(){return 20;} + public override void init(){ + try + { + //md=MessageDigest.getInstance("SHA-1"); + md=new System.Security.Cryptography.SHA1CryptoServiceProvider(); + cs = new System.Security.Cryptography.CryptoStream( System.IO.Stream.Null, md, System.Security.Cryptography.CryptoStreamMode.Write); + } + catch(Exception e){ + Console.WriteLine(e); + } + } + public override void update(byte[] foo, int start, int len){ + //md.update(foo, start, len); + cs.Write(foo, start, len); + } + public override byte[] digest() { + //return md.digest(); + cs.Close(); + byte[] result = md.Hash; + md.Clear(); + init(); //Reinitiazing hash objects + + return result; + } +} + +} diff --git a/SharpSSH/jsch/jce/SignatureDSA.cs b/SharpSSH/jsch/jce/SignatureDSA.cs new file mode 100644 index 00000000..446a23bc --- /dev/null +++ b/SharpSSH/jsch/jce/SignatureDSA.cs @@ -0,0 +1,117 @@ +using System; + +namespace Tamir.SharpSsh.jsch.jce +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + public class SignatureDSA : Tamir.SharpSsh.jsch.SignatureDSA + { + + //java.security.Signature signature; + // KeyFactory keyFactory; + System.Security.Cryptography.DSAParameters DSAKeyInfo; + System.Security.Cryptography.SHA1CryptoServiceProvider sha1; + System.Security.Cryptography.CryptoStream cs; + + public void init() + { + sha1 = new System.Security.Cryptography.SHA1CryptoServiceProvider(); + cs = new System.Security.Cryptography.CryptoStream(System.IO.Stream.Null, sha1, System.Security.Cryptography.CryptoStreamMode.Write); + } + public void setPubKey(byte[] y, byte[] p, byte[] q, byte[] g) + { + DSAKeyInfo.Y = Util.stripLeadingZeros( y ); + DSAKeyInfo.P = Util.stripLeadingZeros( p ) ; + DSAKeyInfo.Q = Util.stripLeadingZeros( q ); + DSAKeyInfo.G = Util.stripLeadingZeros( g ) ; + } + public void setPrvKey(byte[] x, byte[] p, byte[] q, byte[] g) + { + DSAKeyInfo.X = Util.stripLeadingZeros( x ); + DSAKeyInfo.P = Util.stripLeadingZeros( p ); + DSAKeyInfo.Q = Util.stripLeadingZeros( q ); + DSAKeyInfo.G = Util.stripLeadingZeros( g ); + } + + public byte[] sign() + { + //byte[] sig=signature.sign(); + cs.Close(); + System.Security.Cryptography.DSACryptoServiceProvider DSA = new System.Security.Cryptography.DSACryptoServiceProvider(); + DSA.ImportParameters(DSAKeyInfo); + System.Security.Cryptography.DSASignatureFormatter DSAFormatter = new System.Security.Cryptography.DSASignatureFormatter(DSA); + DSAFormatter.SetHashAlgorithm("SHA1"); + + byte[] sig =DSAFormatter.CreateSignature( sha1 ); + return sig; + } + public void update(byte[] foo) + { + //signature.update(foo); + cs.Write( foo , 0, foo.Length); + } + public bool verify(byte[] sig) + { + cs.Close(); + System.Security.Cryptography.DSACryptoServiceProvider DSA = new System.Security.Cryptography.DSACryptoServiceProvider(); + DSA.ImportParameters(DSAKeyInfo); + System.Security.Cryptography.DSASignatureDeformatter DSADeformatter = new System.Security.Cryptography.DSASignatureDeformatter(DSA); + DSADeformatter.SetHashAlgorithm("SHA1"); + + long i=0; + long j=0; + byte[] tmp; + + //This makes sure sig is always 40 bytes? + if(sig[0]==0 && sig[1]==0 && sig[2]==0) + { + long i1 = (sig[i++]<<24)&0xff000000; + long i2 = (sig[i++]<<16)&0x00ff0000; + long i3 = (sig[i++]<<8)&0x0000ff00; + long i4 = (sig[i++])&0x000000ff; + j = i1 | i2 | i3 | i4; + + i+=j; + + i1 = (sig[i++]<<24)&0xff000000; + i2 = (sig[i++]<<16)&0x00ff0000; + i3 = (sig[i++]<<8)&0x0000ff00; + i4 = (sig[i++])&0x000000ff; + j = i1 | i2 | i3 | i4; + + tmp=new byte[j]; + Array.Copy(sig, i, tmp, 0, j); sig=tmp; + } + bool res = DSADeformatter.VerifySignature(sha1, sig); + return res; + } + } + +} diff --git a/SharpSSH/jsch/jce/SignatureRSA.cs b/SharpSSH/jsch/jce/SignatureRSA.cs new file mode 100644 index 00000000..bc2e57af --- /dev/null +++ b/SharpSSH/jsch/jce/SignatureRSA.cs @@ -0,0 +1,157 @@ +using System; + +namespace Tamir.SharpSsh.jsch.jce +{ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + public class SignatureRSA : Tamir.SharpSsh.jsch.SignatureRSA + { + + //java.security.Signature signature; + // KeyFactory keyFactory; + System.Security.Cryptography.RSAParameters RSAKeyInfo; + System.Security.Cryptography.SHA1CryptoServiceProvider sha1; + System.Security.Cryptography.CryptoStream cs; + + public void init() + { + // signature=java.security.Signature.getInstance("SHA1withRSA"); + // keyFactory=KeyFactory.getInstance("RSA"); + sha1 = new System.Security.Cryptography.SHA1CryptoServiceProvider(); + cs = new System.Security.Cryptography.CryptoStream(System.IO.Stream.Null, sha1, System.Security.Cryptography.CryptoStreamMode.Write); + } + public void setPubKey(byte[] e, byte[] n) + { + // RSAPublicKeySpec rsaPubKeySpec = + // new RSAPublicKeySpec(new BigInteger(n), + // new BigInteger(e)); + // PublicKey pubKey=keyFactory.generatePublic(rsaPubKeySpec); + // signature.initVerify(pubKey); + RSAKeyInfo.Modulus = Util.stripLeadingZeros( n ); + RSAKeyInfo.Exponent = e; +// Util.Dump("C:\\e.bin", e); +// Util.Dump("C:\\n.bin", n); + } + public void setPrvKey(byte[] d, byte[] n) + { + // RSAPrivateKeySpec rsaPrivKeySpec = + // new RSAPrivateKeySpec(new BigInteger(n), + // new BigInteger(d)); + // PrivateKey prvKey = keyFactory.generatePrivate(rsaPrivKeySpec); + // signature.initSign(prvKey); + + RSAKeyInfo.D = d ; + RSAKeyInfo.Modulus = n ; + } + + public void setPrvKey(byte[] e, byte[] n, byte[] d, byte[] p, byte[] q, byte[] dp, byte[] dq, byte[] c) + { + RSAKeyInfo.Exponent = e ; + RSAKeyInfo.D = Util.stripLeadingZeros( d ) ; + RSAKeyInfo.Modulus = Util.stripLeadingZeros( n ) ; + RSAKeyInfo.P = Util.stripLeadingZeros(p); + RSAKeyInfo.Q = Util.stripLeadingZeros(q); + RSAKeyInfo.DP = Util.stripLeadingZeros(dp); + RSAKeyInfo.DQ = Util.stripLeadingZeros(dq); + RSAKeyInfo.InverseQ = Util.stripLeadingZeros(c); + } + + public void setPrvKey(System.Security.Cryptography.RSAParameters keyInfo) + { + // RSAPrivateKeySpec rsaPrivKeySpec = + // new RSAPrivateKeySpec(new BigInteger(n), + // new BigInteger(d)); + // PrivateKey prvKey = keyFactory.generatePrivate(rsaPrivKeySpec); + // signature.initSign(prvKey); + RSAKeyInfo = keyInfo; + } + + public byte[] sign() + { + // byte[] sig=signature.sign(); + // return sig; + cs.Close(); + System.Security.Cryptography.RSACryptoServiceProvider RSA = new System.Security.Cryptography.RSACryptoServiceProvider(); + RSA.ImportParameters(RSAKeyInfo); + System.Security.Cryptography.RSAPKCS1SignatureFormatter RSAFormatter = new System.Security.Cryptography.RSAPKCS1SignatureFormatter(RSA); + RSAFormatter.SetHashAlgorithm("SHA1"); + + byte[] sig = RSAFormatter.CreateSignature( sha1 ); + return sig; + + + } + public void update(byte[] foo) + { + //signature.update(foo); + cs.Write( foo , 0, foo.Length); + } + public bool verify(byte[] sig) + { + cs.Close(); + System.Security.Cryptography.RSACryptoServiceProvider RSA = new System.Security.Cryptography.RSACryptoServiceProvider(); + RSA.ImportParameters(RSAKeyInfo); + System.Security.Cryptography.RSAPKCS1SignatureDeformatter RSADeformatter = new System.Security.Cryptography.RSAPKCS1SignatureDeformatter(RSA); + RSADeformatter.SetHashAlgorithm("SHA1"); + + + long i=0; + long j=0; + byte[] tmp; + + //Util.Dump("c:\\sig.bin", sig); + + if(sig[0]==0 && sig[1]==0 && sig[2]==0) + { + long i1 = (sig[i++]<<24)&0xff000000; + long i2 = (sig[i++]<<16)&0x00ff0000; + long i3 = (sig[i++]<<8)&0x0000ff00; + long i4 = (sig[i++])&0x000000ff; + j = i1 | i2 | i3 | i4; + + i+=j; + + i1 = (sig[i++]<<24)&0xff000000; + i2 = (sig[i++]<<16)&0x00ff0000; + i3 = (sig[i++]<<8)&0x0000ff00; + i4 = (sig[i++])&0x000000ff; + j = i1 | i2 | i3 | i4; + + tmp=new byte[j]; + Array.Copy(sig, i, tmp, 0, j); sig=tmp; + } + //System.out.println("j="+j+" "+Integer.toHexString(sig[0]&0xff)); + //return signature.verify(sig); + bool verify = RSADeformatter.VerifySignature(sha1, sig); + return verify; + } + } + +} diff --git a/SharpSSH/jsch/jce/TripleDESCBC.cs b/SharpSSH/jsch/jce/TripleDESCBC.cs new file mode 100644 index 00000000..356d98d1 --- /dev/null +++ b/SharpSSH/jsch/jce/TripleDESCBC.cs @@ -0,0 +1,104 @@ +using System; + +namespace Tamir.SharpSsh.jsch.jce +{ + /* -*-mode:java; c-basic-offset:2; -*- */ + /* + Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + public class TripleDESCBC : Tamir.SharpSsh.jsch.Cipher + { + private const int ivsize=8; + private const int bsize=24; + private System.Security.Cryptography.TripleDES triDes; + private System.Security.Cryptography.ICryptoTransform cipher; + //private javax.crypto.Cipher cipher; + public override int getIVSize(){return ivsize;} + public override int getBlockSize(){return bsize;} + public override void init(int mode, byte[] key, byte[] iv) + { + triDes = new System.Security.Cryptography.TripleDESCryptoServiceProvider(); + triDes.Mode=System.Security.Cryptography.CipherMode.CBC; + triDes.Padding=System.Security.Cryptography.PaddingMode.None; + //String pad="NoPadding"; + //if(padding) pad="PKCS5Padding"; + byte[] tmp; + if(iv.Length>ivsize) + { + tmp=new byte[ivsize]; + Array.Copy(iv, 0, tmp, 0, tmp.Length); + iv=tmp; + } + if(key.Length>bsize) + { + tmp=new byte[bsize]; + Array.Copy(key, 0, tmp, 0, tmp.Length); + key=tmp; + } + + try + { + // cipher=javax.crypto.Cipher.getInstance("DESede/CBC/"+pad); + /* + // The following code does not work on IBM's JDK 1.4.1 + SecretKeySpec skeySpec = new SecretKeySpec(key, "DESede"); + cipher.init((mode==ENCRYPT_MODE? + javax.crypto.Cipher.ENCRYPT_MODE: + javax.crypto.Cipher.DECRYPT_MODE), + skeySpec, new IvParameterSpec(iv)); + */ + // DESedeKeySpec keyspec=new DESedeKeySpec(key); + // SecretKeyFactory keyfactory=SecretKeyFactory.getInstance("DESede"); + // SecretKey _key=keyfactory.generateSecret(keyspec); + // cipher.init((mode==ENCRYPT_MODE? + // javax.crypto.Cipher.ENCRYPT_MODE: + // javax.crypto.Cipher.DECRYPT_MODE), + // _key, new IvParameterSpec(iv)); + cipher = (mode==ENCRYPT_MODE? + triDes.CreateEncryptor(key, iv): + triDes.CreateDecryptor(key, iv)); + } + catch(Exception e) + { + Console.WriteLine(e); + cipher=null; + } + } + public override void update(byte[] foo, int s1, int len, byte[] bar, int s2) + { + // cipher.update(foo, s1, len, bar, s2); + cipher.TransformBlock(foo, s1, len, bar, s2); + } + public override string ToString() + { + return "3des-cbc"; + } + + } + +} diff --git a/SharpSSH/lib/DiffieHellman.dll b/SharpSSH/lib/DiffieHellman.dll new file mode 100644 index 0000000000000000000000000000000000000000..aa3105110d3963b20d2171355c88d5f6b93bad66 GIT binary patch literal 53248 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 literal 0 HcmV?d00001 From 32f1e03e25c15d3467dde86ab4df9c787159e2a8 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Sat, 17 Jul 2010 12:30:17 +0100 Subject: [PATCH 16/37] Add FriendFace library --- FriendFace/FriendFace.cs | 104 ++++++++++++++++ FriendFace/FriendFace.csproj | 1 + FriendFace/Gravatar.cs | 79 ++++++++++++ SharpSSH/SharpSSH.csproj | 200 ++++++++++++++++++++++++++++++- SparkleShare.sln | 6 + SparkleShare/SparkleIntro.cs | 27 ++++- SparkleShare/SparkleShare.csproj | 4 + SparkleShare/SparkleWindow.cs | 5 +- 8 files changed, 416 insertions(+), 10 deletions(-) create mode 100644 FriendFace/FriendFace.cs create mode 100644 FriendFace/FriendFace.csproj create mode 100644 FriendFace/Gravatar.cs diff --git a/FriendFace/FriendFace.cs b/FriendFace/FriendFace.cs new file mode 100644 index 00000000..b59051aa --- /dev/null +++ b/FriendFace/FriendFace.cs @@ -0,0 +1,104 @@ +// FriendFace creates an icon theme of buddy icons from the web +// Copyright (C) 2010 Hylke Bons +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . + +using Gtk; +using System; + +namespace FriendFace +{ + + public class FaceCollection : IconTheme + { + + private string Path; + + public bool UseTwitter; + public bool UseGravatar; + public bool UseIdentica; + public bool UseSystem; + public bool UseFlickr; + + public bool UseAllServices; + + private string IconThemePath; + + public FaceCollection () + { + + Path = ""; + + UseTwitter = false; + UseGravatar = false; + UseIdentica = false; + UseSystem = false; + UseFlickr = false; + UseAllServices = false; + + } + + public FaceCollection (string path) + { + + UnixUserInfo unix_user_info = new UnixUserInfo (UnixEnvironment.UserName); + string home_path = unix_user_info.HomeDirectory; + + string IconThemePath = CombineMore (home_path, ".icons"); + SetThemePath (IconThemePath); + + if (!Directory.Exists (theme_path)) + Directory.CreateDirectory (theme_path); + + Directory.CreateDirectory (CombineMore (ThemePath)); + + + } + + public void SetThemePath (string path) + { + string IconThemePath = path; + } + + public string GetThemePath () + { + + } + + + public Gdk.Pixbuf GetFace (string identifier) + { + return null; + } + + public bool AddFace (string identifier) + { + // avatar-twitter-hbons + // 16, 24, 32, 48 + Gdk.Pixbuf gravatar_icon; + if (UseGravatar) + gravatar_icon = new GravatarIcon (identifier); + + return true; + + } + + public bool Refresh () + { + return true; + } + + } + +} diff --git a/FriendFace/FriendFace.csproj b/FriendFace/FriendFace.csproj new file mode 100644 index 00000000..522e24e6 --- /dev/null +++ b/FriendFace/FriendFace.csproj @@ -0,0 +1 @@ + Debug AnyCPU 8.0.50727 2.0 {3BA434AF-494F-4F5D-9D21-B7BD24FD67AF} Exe FriendFace FriendFace true full false bin\Debug DEBUG prompt 4 none false bin\Release prompt 4 \ No newline at end of file diff --git a/FriendFace/Gravatar.cs b/FriendFace/Gravatar.cs new file mode 100644 index 00000000..c11d799b --- /dev/null +++ b/FriendFace/Gravatar.cs @@ -0,0 +1,79 @@ +// FriendFace creates an icon theme of buddy icons from the web +// Copyright (C) 2010 Hylke Bons +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see .using Gtk; + +using Gtk; +using System; + +namespace FriendFace { + + public class Gravatar : Gdk.Pixbuf { + + public Gravatar (string identifier) + { + + UnixUserInfo unix_user_info = new UnixUserInfo (UnixEnvironment.UserName); + string home_path = unix_user_info.HomeDirectory; + + string theme_path = CombineMore (home_path, ".icons", "friendface", "48x48", "status"); + string file_name = "avatar-gravatar-" + identifier; + string file_path = CombineMore (theme_path, file_name); + + if (!Directory.Exists (theme_path)) + Directory.CreateDirectory (theme_path); + + WebClient WebClient = new WebClient (); + Uri icon_uri = new Uri ("http://www.gravatar.com/avatar/" + MD5 (identifier) + ".jpg?s=48&d=404"); + + if (File.Exists (file_path)) + File.Delete (file_path); + + WebClient.DownloadFileAsync (icon_uri, file_path); + + WebClient.DownloadFileCompleted += delegate { + + FileInfo file_info = new FileInfo (file_path); + + if (file_info.Length < 256) + File.Delete (file_path); + + }; + + } + + } + + + // Creates an MD5 hash of input + public static string MD5 (string s) + { + MD5 md5 = new MD5CryptoServiceProvider (); + Byte[] bytes = ASCIIEncoding.Default.GetBytes (s); + Byte[] encoded_bytes = md5.ComputeHash (bytes); + return BitConverter.ToString (encoded_bytes).ToLower ().Replace ("-", ""); + } + + + // Makes it possible to combine more than + // two paths at once + public static string CombineMore (params string [] parts) + { + string new_path = ""; + foreach (string part in parts) + new_path = Path.Combine (new_path, part); + return new_path; + } + +} diff --git a/SharpSSH/SharpSSH.csproj b/SharpSSH/SharpSSH.csproj index 84636d8c..1e6b181a 100644 --- a/SharpSSH/SharpSSH.csproj +++ b/SharpSSH/SharpSSH.csproj @@ -1 +1,199 @@ - Debug AnyCPU 8.0.50727 2.0 {BB50B7E2-4622-4D8B-B7FF-5E5D8F02D91F} Library Tamir.SharpSsh Tamir.SharpSSH true full false ..\bin\Debug\ prompt 4 false none false ..\bin\Release\ prompt 4 false InputForm.cs False lib\DiffieHellman.dll False lib\Org.Mentalis.Security.dll \ No newline at end of file + + + + Debug + AnyCPU + 8.0.50727 + 2.0 + {BB50B7E2-4622-4D8B-B7FF-5E5D8F02D91F} + Library + Tamir.SharpSsh + Tamir.SharpSSH + + + true + full + false + ..\bin\Debug\ + prompt + 4 + false + + + none + false + ..\bin\Release\ + prompt + 4 + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + InputForm.cs + + + + + + + + + + False + lib\DiffieHellman.dll + + + False + lib\Org.Mentalis.Security.dll + + + + + + + + + \ No newline at end of file diff --git a/SparkleShare.sln b/SparkleShare.sln index df6e25f5..af6794fc 100644 --- a/SparkleShare.sln +++ b/SparkleShare.sln @@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NotifySharp", "NotifySharp\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpSSH", "SharpSSH\SharpSSH.csproj", "{BB50B7E2-4622-4D8B-B7FF-5E5D8F02D91F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FriendFace", "FriendFace\FriendFace.csproj", "{3BA434AF-494F-4F5D-9D21-B7BD24FD67AF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -17,6 +19,10 @@ Global {005CCA8E-DFBF-464A-B6DA-452C62D4589C}.Debug|Any CPU.Build.0 = Debug|Any CPU {005CCA8E-DFBF-464A-B6DA-452C62D4589C}.Release|Any CPU.ActiveCfg = Release|Any CPU {005CCA8E-DFBF-464A-B6DA-452C62D4589C}.Release|Any CPU.Build.0 = Release|Any CPU + {3BA434AF-494F-4F5D-9D21-B7BD24FD67AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3BA434AF-494F-4F5D-9D21-B7BD24FD67AF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3BA434AF-494F-4F5D-9D21-B7BD24FD67AF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3BA434AF-494F-4F5D-9D21-B7BD24FD67AF}.Release|Any CPU.Build.0 = Release|Any CPU {728483AA-E34B-4441-BF2C-C8BC2901E4E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {728483AA-E34B-4441-BF2C-C8BC2901E4E0}.Debug|Any CPU.Build.0 = Debug|Any CPU {728483AA-E34B-4441-BF2C-C8BC2901E4E0}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/SparkleShare/SparkleIntro.cs b/SparkleShare/SparkleIntro.cs index 005e25a3..1342da62 100644 --- a/SparkleShare/SparkleIntro.cs +++ b/SparkleShare/SparkleIntro.cs @@ -23,11 +23,11 @@ using System.Diagnostics; using System.IO; using System.Text.RegularExpressions; using System.Timers; +using System.Security.Cryptography; namespace SparkleShare { - public class SparkleIntro : Window - { + public class SparkleIntro : Window { // Short alias for the translations public static string _ (string s) @@ -38,15 +38,29 @@ namespace SparkleShare { public SparkleIntro () : base ("") { + using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider()) + { + File.WriteAllText ("PublicKeyOnly.xml", rsa.ToXmlString (false)); + File.WriteAllText ("PublicPrivate.xml", rsa.ToXmlString (true)); + } + BorderWidth = 0; SetSizeRequest (640, 400); Resizable = false; IconName = "folder-sparkleshare"; WindowPosition = WindowPosition.Center; + + ShowStepOne (); + + } + + public void ShowStepOne () + { HBox layout_horizontal = new HBox (false, 6); + // TODO: Fix the path Image side_splash = new Image ("/home/hbons/github/SparkleShare/data/side-splash.png"); layout_horizontal.PackStart (side_splash, false, false, 0); @@ -143,10 +157,10 @@ namespace SparkleShare { layout_horizontal.PackStart (wrapper, true, true, 0); Add (layout_horizontal); - ShowAll (); - } + ShowAll (); + } public void ShowStepTwo () { @@ -165,7 +179,7 @@ namespace SparkleShare { layout_vertical.BorderWidth = 30; Label introduction; - introduction = new Label ("SparkleShare ready to go!"); + introduction = new Label ("SparkleShare is ready to go!"); introduction.UseMarkup = true; introduction.Xalign = 0; @@ -173,7 +187,7 @@ namespace SparkleShare { Label information; information = new Label ("You can now start accepting invitations from others. " + "Just click on invitations you get by email and " + - "we'll take care of the rest."); + "we will take care of the rest."); information.UseMarkup = true; information.Wrap = true; @@ -207,6 +221,7 @@ namespace SparkleShare { layout_horizontal.Add (wrapper); Add (layout_horizontal); + ShowAll (); } diff --git a/SparkleShare/SparkleShare.csproj b/SparkleShare/SparkleShare.csproj index f42c092b..62026f09 100644 --- a/SparkleShare/SparkleShare.csproj +++ b/SparkleShare/SparkleShare.csproj @@ -33,6 +33,10 @@ + + False + ..\SharpSSH\lib\DiffieHellman.dll + diff --git a/SparkleShare/SparkleWindow.cs b/SparkleShare/SparkleWindow.cs index 5bcdc15e..5cac9ba3 100644 --- a/SparkleShare/SparkleWindow.cs +++ b/SparkleShare/SparkleWindow.cs @@ -26,8 +26,7 @@ using System.Timers; namespace SparkleShare { - public class SparkleWindow : Window - { + public class SparkleWindow : Window { // Short alias for the translations public static string _ (string s) @@ -137,7 +136,7 @@ namespace SparkleShare { DateTime date_time = UnixTimestampToDateTime (unix_timestamp); - message = message.Replace ("/", " → "); + message = message.Replace ("/", " ‣ "); message = message.Replace ("\n", " "); ChangeSet change_set = new ChangeSet (user_name, user_email, message, date_time); From 3b4190baef484561e2546043d195762f5437b355 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Sun, 18 Jul 2010 19:38:34 +0100 Subject: [PATCH 17/37] Continue work on FriendFace --- FriendFace/FriendFace.cs | 147 +++++++++++++++++++---------- FriendFace/Gravatar.cs | 79 ---------------- FriendFace/GravatarIconProvider.cs | 66 +++++++++++++ FriendFace/IconProvider.cs | 110 +++++++++++++++++++++ SparkleDiff/Makefile.am | 3 + SparkleDiff/SparkleDiffWindow.cs | 96 ++----------------- 6 files changed, 285 insertions(+), 216 deletions(-) delete mode 100644 FriendFace/Gravatar.cs create mode 100644 FriendFace/GravatarIconProvider.cs create mode 100644 FriendFace/IconProvider.cs diff --git a/FriendFace/FriendFace.cs b/FriendFace/FriendFace.cs index b59051aa..19748228 100644 --- a/FriendFace/FriendFace.cs +++ b/FriendFace/FriendFace.cs @@ -15,88 +15,137 @@ // along with this program. If not, see . using Gtk; +using Mono.Unix; using System; +using System.IO; -namespace FriendFace -{ +namespace FriendFace { - public class FaceCollection : IconTheme - { + public class FaceCollection : IconTheme { - private string Path; - - public bool UseTwitter; + public bool UseFlickr; public bool UseGravatar; public bool UseIdentica; - public bool UseSystem; - public bool UseFlickr; - - public bool UseAllServices; + public bool UseTwitter; - private string IconThemePath; + private string Path; public FaceCollection () { - Path = ""; - - UseTwitter = false; - UseGravatar = false; - UseIdentica = false; - UseSystem = false; - UseFlickr = false; - UseAllServices = false; + UnixUserInfo unix_user_info = new UnixUserInfo (UnixEnvironment.UserName); + string default_theme_path = CombineMore (unix_user_info.HomeDirectory, ".icons"); + new FaceCollection (default_theme_path); } + public FaceCollection (string path) { - - UnixUserInfo unix_user_info = new UnixUserInfo (UnixEnvironment.UserName); - string home_path = unix_user_info.HomeDirectory; - - string IconThemePath = CombineMore (home_path, ".icons"); - SetThemePath (IconThemePath); - - if (!Directory.Exists (theme_path)) - Directory.CreateDirectory (theme_path); - - Directory.CreateDirectory (CombineMore (ThemePath)); - - + CustomTheme = "FriendFace"; + SetThemePath (path); } - + + public void SetThemePath (string path) { - string IconThemePath = path; + + Path = path; + + if (!Directory.Exists (Path)) + Directory.CreateDirectory (Path); + + AppendSearchPath (path); + + Refresh (); + } + public string GetThemePath () { - + return Path; } - - public Gdk.Pixbuf GetFace (string identifier) + + public Gdk.Pixbuf GetFace (string identifier, int size) { - return null; + return LoadIcon ("avatar-default-" + identifier, size, IconLookupFlags.GenericFallback); } - + + + public void Refresh () + {foreach (string i in SearchPath) {Console.WriteLine (i);} + + IconProvider provider = new IconProvider (""); + string folder = provider.GetTargetFolderPath (); + string [] files = Directory.GetFiles (folder); + + int [] sizes = {16, 24, 32, 48}; + + foreach (string file_path in files) { + + Gdk.Pixbuf pixbuf = new Gdk.Pixbuf (file_path); + + FileInfo file_info = new FileInfo (file_path); + + // Delete the icon if it turns out to be empty + if (file_info.Length == 0) { + File.Delete (file_path); + Console.WriteLine ("Deleted: " + file_path); + } + + for (int i = 0; i < 4; i++) { + + int size = sizes [i]; + + Gdk.Pixbuf pixbuf_copy = pixbuf.Copy (); + + if (pixbuf.Width != size || pixbuf.Height != size) + pixbuf_copy = pixbuf.ScaleSimple (size, size, Gdk.InterpType.Hyper); + + string size_folder_path = CombineMore (Path, size + "x" + size, "status"); + string size_file_path = CombineMore (size_folder_path, System.IO.Path.GetFileName (file_path)); + + Directory.CreateDirectory (size_folder_path); + + if (File.Exists (size_file_path)) + File.Delete (size_file_path); + + pixbuf_copy.Save (size_file_path, "png"); + + } + + File.Delete (file_path); + + } + + RescanIfNeeded (); + + } + + public bool AddFace (string identifier) { - // avatar-twitter-hbons - // 16, 24, 32, 48 - Gdk.Pixbuf gravatar_icon; - if (UseGravatar) - gravatar_icon = new GravatarIcon (identifier); + + if (UseGravatar) { + GravatarIconProvider provider = new GravatarIconProvider (identifier); + provider.RetrieveIcon (); + } return true; - + } - - public bool Refresh () + + + // Makes it possible to combine more than + // two paths at once + private string CombineMore (params string [] parts) { - return true; + string new_path = ""; + foreach (string part in parts) + new_path = System.IO.Path.Combine (new_path, part); + return new_path; } } diff --git a/FriendFace/Gravatar.cs b/FriendFace/Gravatar.cs deleted file mode 100644 index c11d799b..00000000 --- a/FriendFace/Gravatar.cs +++ /dev/null @@ -1,79 +0,0 @@ -// FriendFace creates an icon theme of buddy icons from the web -// Copyright (C) 2010 Hylke Bons -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library 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 -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this program. If not, see .using Gtk; - -using Gtk; -using System; - -namespace FriendFace { - - public class Gravatar : Gdk.Pixbuf { - - public Gravatar (string identifier) - { - - UnixUserInfo unix_user_info = new UnixUserInfo (UnixEnvironment.UserName); - string home_path = unix_user_info.HomeDirectory; - - string theme_path = CombineMore (home_path, ".icons", "friendface", "48x48", "status"); - string file_name = "avatar-gravatar-" + identifier; - string file_path = CombineMore (theme_path, file_name); - - if (!Directory.Exists (theme_path)) - Directory.CreateDirectory (theme_path); - - WebClient WebClient = new WebClient (); - Uri icon_uri = new Uri ("http://www.gravatar.com/avatar/" + MD5 (identifier) + ".jpg?s=48&d=404"); - - if (File.Exists (file_path)) - File.Delete (file_path); - - WebClient.DownloadFileAsync (icon_uri, file_path); - - WebClient.DownloadFileCompleted += delegate { - - FileInfo file_info = new FileInfo (file_path); - - if (file_info.Length < 256) - File.Delete (file_path); - - }; - - } - - } - - - // Creates an MD5 hash of input - public static string MD5 (string s) - { - MD5 md5 = new MD5CryptoServiceProvider (); - Byte[] bytes = ASCIIEncoding.Default.GetBytes (s); - Byte[] encoded_bytes = md5.ComputeHash (bytes); - return BitConverter.ToString (encoded_bytes).ToLower ().Replace ("-", ""); - } - - - // Makes it possible to combine more than - // two paths at once - public static string CombineMore (params string [] parts) - { - string new_path = ""; - foreach (string part in parts) - new_path = Path.Combine (new_path, part); - return new_path; - } - -} diff --git a/FriendFace/GravatarIconProvider.cs b/FriendFace/GravatarIconProvider.cs new file mode 100644 index 00000000..1eee1f02 --- /dev/null +++ b/FriendFace/GravatarIconProvider.cs @@ -0,0 +1,66 @@ +// FriendFace creates an icon theme of buddy icons from the web +// Copyright (C) 2010 Hylke Bons +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see .using Gtk; + +using Gtk; +using Mono.Unix; +using System; +using System.IO; +using System.Net; +using System.Security.Cryptography; +using System.Text; + +namespace FriendFace { + + public class GravatarIconProvider : IconProvider { + + public GravatarIconProvider (string email) : base (email) + { + ServiceName = "gravatar"; + } + + + new public void RetrieveIcon () + { + + string target_file_path = GetTargetFilePath (); + + if (File.Exists (target_file_path)) + return; + + WebClient web_client = new WebClient (); + Uri icon_uri = new Uri ("http://www.gravatar.com/avatar/" + MD5 (Identifier) + ".jpg?s=48&d=404"); + + web_client.DownloadFileAsync (icon_uri, target_file_path); + + web_client.DownloadFileCompleted += delegate { + base.RetrieveIcon (); + }; + + } + + + // Creates an MD5 hash of input + public static string MD5 (string s) + { + MD5 md5 = new MD5CryptoServiceProvider (); + Byte[] bytes = ASCIIEncoding.Default.GetBytes (s); + Byte[] encoded_bytes = md5.ComputeHash (bytes); + return BitConverter.ToString (encoded_bytes).ToLower ().Replace ("-", ""); + } + + } + +} diff --git a/FriendFace/IconProvider.cs b/FriendFace/IconProvider.cs new file mode 100644 index 00000000..c4717c4b --- /dev/null +++ b/FriendFace/IconProvider.cs @@ -0,0 +1,110 @@ +// IconProvider is a base class that can be extended to pull buddy +// icons from sources so that they can be used by FaceCollection. +// +// Copyright (C) 2010 Hylke Bons +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see .using Gtk; + +using Gtk; +using Mono.Unix; +using System; +using System.IO; + +namespace FriendFace { + + public class IconProvider { + + public string Identifier; + public string ServiceName; + + private string TargetFolderPath; + private string TargetFilePath; + + public IconProvider (string identifier) + { + + Identifier = identifier; + + string XDG_CACHE_HOME = Environment.GetEnvironmentVariable ("XDG_CACHE_HOME"); + + if (XDG_CACHE_HOME != null) + SetTargetFolderPath (CombineMore (XDG_CACHE_HOME, "friendface")); + else + SetTargetFolderPath (CombineMore (System.IO.Path.DirectorySeparatorChar.ToString (), "tmp", "friendface")); + + string file_name = "avatar-default-" + Identifier; + TargetFilePath = CombineMore (TargetFolderPath, file_name); + + } + + + public void RetrieveIcon () + { + + if (File.Exists (TargetFilePath)) { + + FileInfo file_info = new FileInfo (TargetFilePath); + + // Delete the icon if it turns out to be empty + if (file_info.Length == 0) { + File.Delete (TargetFilePath); + Console.WriteLine ("Deleted: " + TargetFilePath); + } + + } + + } + + + public void SetTargetFolderPath (string path) + { + + TargetFolderPath = path; + + if (!Directory.Exists (TargetFolderPath)) + Directory.CreateDirectory (TargetFolderPath); + + } + + + public string GetTargetFolderPath () + { + return TargetFolderPath; + } + + + public void SetTargetFilePath (string path) + { + TargetFilePath = path; + } + + + public string GetTargetFilePath () + { + return TargetFilePath; + } + + + // Makes it possible to combine more than two paths at once + public string CombineMore (params string [] parts) + { + string new_path = ""; + foreach (string part in parts) + new_path = Path.Combine (new_path, part); + return new_path; + } + + } + +} diff --git a/SparkleDiff/Makefile.am b/SparkleDiff/Makefile.am index 59fd06ce..9651f73f 100644 --- a/SparkleDiff/Makefile.am +++ b/SparkleDiff/Makefile.am @@ -5,6 +5,9 @@ LINK = $(REF_SPARKLEDIFF) SOURCES = \ $(top_srcdir)/SparkleShare/Defines.cs \ +$(top_srcdir)/FriendFace/FriendFace.cs \ +$(top_srcdir)/FriendFace/IconProvider.cs \ +$(top_srcdir)/FriendFace/GravatarIconProvider.cs \ SparkleDiff.cs \ SparkleDiffWindow.cs \ RevisionView.cs \ diff --git a/SparkleDiff/SparkleDiffWindow.cs b/SparkleDiff/SparkleDiffWindow.cs index d4a831fa..1b69dd62 100644 --- a/SparkleDiff/SparkleDiffWindow.cs +++ b/SparkleDiff/SparkleDiffWindow.cs @@ -14,17 +14,13 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +using FriendFace; using Gtk; using Mono.Unix; using System; using System.Diagnostics; using System.Text.RegularExpressions; -using System.IO; -using System.Net; -using System.Security.Cryptography; -using System.Text; - namespace SparkleShare { // The main window for SparkleDiff @@ -52,6 +48,9 @@ namespace SparkleShare { BorderWidth = 12; IconName = "image-x-generic"; + FaceCollection face_collection = new FaceCollection (); + face_collection.UseGravatar = true; + DeleteEvent += Quit; Title = file_name; @@ -94,8 +93,10 @@ namespace SparkleShare { UnixTimestampToDateTime (timestamp).ToString (_("ddd MMM d, yyyy")), UnixTimestampToDateTime (timestamp).ToString (_("H:mm"))); - ViewLeft.AddRow (GetAvatar (email, 32), author, date); - ViewRight.AddRow (GetAvatar (email, 32), author, date); + face_collection.AddFace (email); + + ViewLeft.AddRow (face_collection.GetFace (email, 32), author, date); + ViewRight.AddRow (face_collection.GetFace (email, 32), author, date); i++; @@ -243,15 +244,6 @@ namespace SparkleShare { } - public string CombineMore (params string [] Parts) - { - string NewPath = " "; - foreach (string Part in Parts) - NewPath = System.IO.Path.Combine (NewPath, Part); - return NewPath; - } - - // Converts a UNIX timestamp to a more usable time object public DateTime UnixTimestampToDateTime (int timestamp) { @@ -260,78 +252,6 @@ namespace SparkleShare { } - // Looks up an icon from the system's theme - public Gdk.Pixbuf GetIcon (string name, int size) - { - IconTheme icon_theme = new IconTheme (); - icon_theme.AppendSearchPath (System.IO.Path.Combine ("/usr/share/sparkleshare", "icons")); - return icon_theme.LoadIcon (name, size, IconLookupFlags.GenericFallback); - } - - - // Creates an MD5 hash of input - public static string GetMD5 (string s) - { - MD5 md5 = new MD5CryptoServiceProvider (); - Byte[] bytes = ASCIIEncoding.Default.GetBytes (s); - Byte[] encodedBytes = md5.ComputeHash (bytes); - return BitConverter.ToString (encodedBytes).ToLower ().Replace ("-", ""); - } - - - // TODO: Turn this into an avatar fetching library - // Gets the avatar for a specific email address and size - public Gdk.Pixbuf GetAvatar (string Email, int Size) - { - - UnixUserInfo UnixUserInfo = new UnixUserInfo (UnixEnvironment.UserName); - - string HomePath = UnixUserInfo.HomeDirectory; - - string SparkleLocalIconPath = CombineMore (HomePath, ".icons", "sparkleshare"); - - string AvatarPath = CombineMore (SparkleLocalIconPath, Size + "x" + Size, "status"); - - if (!Directory.Exists (AvatarPath)) { - Directory.CreateDirectory (AvatarPath); - } - - string AvatarFilePath = CombineMore (AvatarPath, Email); - - if (File.Exists (AvatarFilePath)) - return new Gdk.Pixbuf (AvatarFilePath); - else { - - // Let's try to get the person's gravatar for next time - WebClient WebClient = new WebClient (); - Uri GravatarUri = new Uri ("http://www.gravatar.com/avatar/" + GetMD5 (Email) + - ".jpg?s=" + Size + "&d=404"); - - string TmpFile = CombineMore (HomePath, "SparkleShare", ".tmp", Email + Size); - - if (!File.Exists (TmpFile)) { - - WebClient.DownloadFileAsync (GravatarUri, TmpFile); - WebClient.DownloadFileCompleted += delegate { - File.Delete (AvatarFilePath); - FileInfo TmpFileInfo = new FileInfo (TmpFile); - if (TmpFileInfo.Length > 255) - File.Move (TmpFile, AvatarFilePath); - }; - - } - - // Fall back to a generic icon if there is no gravatar - if (File.Exists (AvatarFilePath)) - return new Gdk.Pixbuf (AvatarFilePath); - else - return GetIcon ("avatar-default", Size); - - } - - } - - // Quits the program private void Quit (object o, EventArgs args) { From 805b7b4c89cc76ea231760c426619bacf8c25c7c Mon Sep 17 00:00:00 2001 From: Simon Pither Date: Sat, 3 Jul 2010 20:37:43 +0100 Subject: [PATCH 18/37] Avoid race condition on changes. Ensure timers restart even on exceptions. --- SparkleShare/SparkleRepo.cs | 84 +++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 37 deletions(-) diff --git a/SparkleShare/SparkleRepo.cs b/SparkleShare/SparkleRepo.cs index 8d856d6a..4924ed81 100644 --- a/SparkleShare/SparkleRepo.cs +++ b/SparkleShare/SparkleRepo.cs @@ -35,6 +35,7 @@ namespace SparkleShare { private FileSystemWatcher Watcher; private bool HasChanged = false; private DateTime LastChange; + private System.Object ChangeLock = new System.Object(); public string Name; public string Domain; @@ -126,14 +127,16 @@ namespace SparkleShare { BufferTimer.Elapsed += delegate (object o, ElapsedEventArgs args) { SparkleHelpers.DebugInfo ("Buffer", "[" + Name + "] Checking for changes."); - if (HasChanged) { - SparkleHelpers.DebugInfo ("Buffer", "[" + Name + "] Changes found, checking if settled."); - DateTime now = DateTime.UtcNow; - TimeSpan changed = new TimeSpan (now.Ticks - LastChange.Ticks); - if (changed.TotalMilliseconds > 5000) { - HasChanged = false; - SparkleHelpers.DebugInfo ("Buffer", "[" + Name + "] Changes have settled, adding."); - AddCommitAndPush (); + lock(ChangeLock) { + if (HasChanged) { + SparkleHelpers.DebugInfo ("Buffer", "[" + Name + "] Changes found, checking if settled."); + DateTime now = DateTime.UtcNow; + TimeSpan changed = new TimeSpan (now.Ticks - LastChange.Ticks); + if (changed.TotalMilliseconds > 5000) { + HasChanged = false; + SparkleHelpers.DebugInfo ("Buffer", "[" + Name + "] Changes have settled, adding."); + AddCommitAndPush (); + } } } }; @@ -156,8 +159,10 @@ namespace SparkleShare { if (!ShouldIgnore (args.Name)) { SparkleHelpers.DebugInfo ("Event", "[" + Name + "] " + wct.ToString () + " '" + args.Name + "'"); FetchTimer.Stop (); - LastChange = DateTime.UtcNow; - HasChanged = true; + lock(ChangeLock) { + LastChange = DateTime.UtcNow; + HasChanged = true; + } } } @@ -165,22 +170,23 @@ namespace SparkleShare { // so this method does them all with appropriate timers, etc switched off public void AddCommitAndPush () { - BufferTimer.Stop (); - FetchTimer.Stop (); - - Add (); - string Message = FormatCommitMessage (); - if (!Message.Equals ("")) { - Commit (Message); - Fetch (); - Push (); + try { + BufferTimer.Stop (); + FetchTimer.Stop (); + + Add (); + string Message = FormatCommitMessage (); + if (!Message.Equals ("")) { + Commit (Message); + Fetch (); + Push (); + SparkleHelpers.CheckForUnicorns (Message); + } + } + finally { + FetchTimer.Start (); + BufferTimer.Start (); } - - FetchTimer.Start (); - BufferTimer.Start (); - - SparkleHelpers.CheckForUnicorns (Message); - } // Stages the made changes @@ -207,18 +213,22 @@ namespace SparkleShare { // Fetches changes from the remote repo public void Fetch () { - FetchTimer.Stop (); -// SparkleUI.NotificationIcon.SetSyncingState (); - SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Fetching changes..."); - Process.StartInfo.Arguments = "fetch -v"; - Process.Start (); - string Output = Process.StandardOutput.ReadToEnd ().Trim (); // TODO: This doesn't work :( - Process.WaitForExit (); - SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Changes fetched."); - if (!Output.Contains ("up to date")) - Rebase (); -// SparkleUI.NotificationIcon.SetIdleState (); - FetchTimer.Start (); + try { + FetchTimer.Stop (); +// SparkleUI.NotificationIcon.SetSyncingState (); + SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Fetching changes..."); + Process.StartInfo.Arguments = "fetch -v"; + Process.Start (); + string Output = Process.StandardOutput.ReadToEnd ().Trim (); // TODO: This doesn't work :( + Process.WaitForExit (); + SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Changes fetched."); + if (!Output.Contains ("up to date")) + Rebase (); +// SparkleUI.NotificationIcon.SetIdleState (); + } + finally { + FetchTimer.Start (); + } } // Merges the fetched changes From f19053919e357a175ad9388acaa67e3b7ca1d6c0 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Mon, 19 Jul 2010 22:17:20 +0100 Subject: [PATCH 19/37] Remove loose files --- PublicKeyOnly.xml | 1 - PublicPrivate.xml | 1 - 2 files changed, 2 deletions(-) delete mode 100644 PublicKeyOnly.xml delete mode 100644 PublicPrivate.xml diff --git a/PublicKeyOnly.xml b/PublicKeyOnly.xml deleted file mode 100644 index fb77bdd0..00000000 --- a/PublicKeyOnly.xml +++ /dev/null @@ -1 +0,0 @@ -oQj45pN1dw2wemY0G8OtqxKqOeiaczy6R9Uy5eD5Ldfx3m3c48zSgzPFFYW8EQM94TcG//4uPZIiJVA9GnrlINOsmUJvqJTAepep8itjlY/MyeQUxqyfYNW1vecz/X8sgmAbWk2XBZM8Enf3UqtYvWP7+zOt4sqCLpWnm+7nzy0=EQ== \ No newline at end of file diff --git a/PublicPrivate.xml b/PublicPrivate.xml deleted file mode 100644 index 69eb8f03..00000000 --- a/PublicPrivate.xml +++ /dev/null @@ -1 +0,0 @@ -oQj45pN1dw2wemY0G8OtqxKqOeiaczy6R9Uy5eD5Ldfx3m3c48zSgzPFFYW8EQM94TcG//4uPZIiJVA9GnrlINOsmUJvqJTAepep8itjlY/MyeQUxqyfYNW1vecz/X8sgmAbWk2XBZM8Enf3UqtYvWP7+zOt4sqCLpWnm+7nzy0=EQ==

sIQ90D2XsEXpxLDmUnzISsHrcDS4+cmhRq8Jb91Kd/Kk+CPEVthIvUeYmCoNnWP3BdccaeDK6PFz0Db06r/nqw==

6YwdeMA0LL4wEFBMT7BfC7PFXyA1w5q1+9jEXb9/QWlI177B2Sb0CHQ5XbifeZws5p+XMWo/4vNqXcVWn2nshw==fJmVC3bFbV6G5TGThYVCFqcAi3CCkjP5XxIkx28HY7pWVM30AREkSV+3Af+RI84Xx+MjHY+eShPZR65Sh5aFaQ==Um2wDIASaiUB56Pesra4Ij9y1keagUWppC5jbGG0cXB0D+j5H3co8+zI8+bO36CIUWWAqAdh16E0mZDxZXCt1Q==TAlt1Bz3uoh20LyceZ2tvXBP+qkeR2iny0YA8IZPNeXYK9J+/grFJx4fBS7Y9h6zgWTJyDfv7ocLpGctJd+rgg==eyT6kjSHALAdisagUXeEzh1U/xtI7rX33JP5vtk3BPBtqhfHCI2R69xLas+t7uRcfwv2S0nnIAZWWMTjbpo2vYZoa6CiXX1x76PqdFneVEn3T2o4WdUPB1JubE/N+moX+SL+R9plAveAMVRqJERDwKaxNr/THaSWaqmhH1wYzv0=
\ No newline at end of file From a0af75cc7837af51a76daa65707c51f321aa4595 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Mon, 19 Jul 2010 23:01:23 +0100 Subject: [PATCH 20/37] [sparkleintro] Create a .gitconfig with user data --- SparkleShare/SparkleIntro.cs | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/SparkleShare/SparkleIntro.cs b/SparkleShare/SparkleIntro.cs index 1342da62..bfdf350a 100644 --- a/SparkleShare/SparkleIntro.cs +++ b/SparkleShare/SparkleIntro.cs @@ -38,11 +38,11 @@ namespace SparkleShare { public SparkleIntro () : base ("") { - using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider()) - { - File.WriteAllText ("PublicKeyOnly.xml", rsa.ToXmlString (false)); - File.WriteAllText ("PublicPrivate.xml", rsa.ToXmlString (true)); - } +// using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider()) + // { + // File.WriteAllText ("PublicKeyOnly.xml", rsa.ToXmlString (false)); + // File.WriteAllText ("PublicPrivate.xml", rsa.ToXmlString (true)); + //} BorderWidth = 0; SetSizeRequest (640, 400); @@ -140,7 +140,24 @@ namespace SparkleShare { done_button.Sensitive = false; table.Sensitive = false; done_button.ShowAll (); + + string user_name = name_entry.Text; + string user_email = email_entry.Text; + + string config_file_path = SparkleHelpers.CombineMore (SparklePaths.SparkleConfigPath, ".gitconfig"); + + TextWriter writer = new StreamWriter (config_file_path); + + writer.WriteLine ("[user]\n" + + "\tname = " + user_name + "\n" + + "\temail = " + user_email + "\n"); + + writer.Close (); + + SparkleHelpers.DebugInfo ("Config", "Created '" + config_file_path + "'"); + ShowStepTwo (); + }; controls.Add (done_button); From 3382c9009d6a5cd8fd1c7636aa649b4be681ec4c Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Mon, 19 Jul 2010 23:02:14 +0100 Subject: [PATCH 21/37] [sparklerepo] don't configure username every time sparkleshare is started --- SparkleShare/SparkleRepo.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/SparkleShare/SparkleRepo.cs b/SparkleShare/SparkleRepo.cs index 39171b5d..5346412c 100644 --- a/SparkleShare/SparkleRepo.cs +++ b/SparkleShare/SparkleRepo.cs @@ -70,10 +70,6 @@ namespace SparkleShare { else UserName = UnixUserInfo.RealName; - Process.StartInfo.FileName = "git"; - Process.StartInfo.Arguments = "config user.name " + UserName; - Process.Start (); - // Get user.email, example: "user@github.com" UserEmail = "not.set@git-scm.com"; Process.StartInfo.FileName = "git"; From f2c6451b1799c4e2c3b4b1e4ba82bbcb39ea04ff Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Mon, 19 Jul 2010 23:03:10 +0100 Subject: [PATCH 22/37] [sparklewindow] don't show full url in event log windows --- SparkleShare/SparkleWindow.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SparkleShare/SparkleWindow.cs b/SparkleShare/SparkleWindow.cs index 5cac9ba3..ffed1261 100644 --- a/SparkleShare/SparkleWindow.cs +++ b/SparkleShare/SparkleWindow.cs @@ -47,8 +47,7 @@ namespace SparkleShare { BorderWidth = 12; // TRANSLATORS: {0} is a folder name, and {1} is a server address - Title = String.Format(_("Recent Events in ‘{0}’ on {1}"), SparkleRepo.Name, - SparkleRepo.RemoteOriginUrl); + Title = String.Format(_("Recent Events in ‘{0}’"), SparkleRepo.Name); IconName = "folder"; LayoutVertical = new VBox (false, 12); From 7c42ba2cdedfee99967179a26bf54ac10057139a Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Tue, 20 Jul 2010 22:21:37 +0100 Subject: [PATCH 23/37] Code cleanup --- SparkleShare/SparkleRepo.cs | 401 +++++++++++++++++++++++----------- SparkleShare/SparkleWindow.cs | 47 ++-- 2 files changed, 301 insertions(+), 147 deletions(-) diff --git a/SparkleShare/SparkleRepo.cs b/SparkleShare/SparkleRepo.cs index 5346412c..701b493b 100644 --- a/SparkleShare/SparkleRepo.cs +++ b/SparkleShare/SparkleRepo.cs @@ -42,7 +42,6 @@ namespace SparkleShare { public string LocalPath; public string RemoteOriginUrl; public string CurrentHash; - public string UserEmail; public string UserName; @@ -51,181 +50,314 @@ namespace SparkleShare { return Catalog.GetString (s); } - public SparkleRepo (string RepoPath) + public SparkleRepo (string path) { + LocalPath = path; + Name = Path.GetFileName (LocalPath); + Process = new Process (); Process.EnableRaisingEvents = true; Process.StartInfo.RedirectStandardOutput = true; Process.StartInfo.UseShellExecute = false; - - // Get the repository's path, example: "/home/user/SparkleShare/repo" - LocalPath = RepoPath; + Process.StartInfo.FileName = "git"; Process.StartInfo.WorkingDirectory = LocalPath; - // Get user.name, example: "User Name" - UnixUserInfo UnixUserInfo = new UnixUserInfo (UnixEnvironment.UserName); - if (UnixUserInfo.RealName.Equals ("")) - UserName = "Anonymous"; - else - UserName = UnixUserInfo.RealName; - - // Get user.email, example: "user@github.com" - UserEmail = "not.set@git-scm.com"; - Process.StartInfo.FileName = "git"; - Process.StartInfo.Arguments = "config --get user.email"; - Process.Start (); - UserEmail = Process.StandardOutput.ReadToEnd ().Trim (); - - // Get remote.origin.url, example: "ssh://git@github.com/user/repo" - Process.StartInfo.FileName = "git"; - Process.StartInfo.Arguments = "config --get remote.origin.url"; - Process.Start (); - RemoteOriginUrl = Process.StandardOutput.ReadToEnd ().Trim (); - - // Get the repository name, example: "Project" - Name = Path.GetFileName (LocalPath); - - // Get the domain, example: "github.com" - Domain = RemoteOriginUrl; - Domain = Domain.Substring (Domain.IndexOf ("@") + 1); - if (Domain.IndexOf (":") > -1) - Domain = Domain.Substring (0, Domain.IndexOf (":")); - else - Domain = Domain.Substring (0, Domain.IndexOf ("/")); - - // Get hash of the current commit - Process.StartInfo.FileName = "git"; - Process.StartInfo.Arguments = "rev-list --max-count=1 HEAD"; - Process.Start (); - CurrentHash = Process.StandardOutput.ReadToEnd ().Trim (); + UserName = GetUserName (); + UserEmail = GetUserEmail (); + RemoteOriginUrl = GetRemoteOriginUrl (); + CurrentHash = GetCurrentHash (); + Domain = GetDomain (RemoteOriginUrl); // Watch the repository's folder - Watcher = new FileSystemWatcher (LocalPath); - Watcher.IncludeSubdirectories = true; - Watcher.EnableRaisingEvents = true; - Watcher.Filter = "*"; + Watcher = new FileSystemWatcher (LocalPath) { + IncludeSubdirectories = true, + EnableRaisingEvents = true, + Filter = "*" + }; + Watcher.Changed += new FileSystemEventHandler (OnFileActivity); Watcher.Created += new FileSystemEventHandler (OnFileActivity); Watcher.Deleted += new FileSystemEventHandler (OnFileActivity); // Fetch remote changes every 20 seconds - FetchTimer = new Timer (); - FetchTimer.Interval = 20000; + FetchTimer = new Timer () { + Interval = 20000 + }; + FetchTimer.Elapsed += delegate { Fetch (); }; - FetchTimer.Start (); - - BufferTimer = new Timer (); - BufferTimer.Interval = 4000; - BufferTimer.Elapsed += delegate (object o, ElapsedEventArgs args) { - SparkleHelpers.DebugInfo ("Buffer", "[" + Name + "] Checking for changes."); - - lock(ChangeLock) { - if (HasChanged) { - SparkleHelpers.DebugInfo ("Buffer", "[" + Name + "] Changes found, checking if settled."); - DateTime now = DateTime.UtcNow; - TimeSpan changed = new TimeSpan (now.Ticks - LastChange.Ticks); - if (changed.TotalMilliseconds > 5000) { - HasChanged = false; - SparkleHelpers.DebugInfo ("Buffer", "[" + Name + "] Changes have settled, adding."); - AddCommitAndPush (); - } - } - } + // Keep a buffer that checks if there are changes and + // whether they have settled + BufferTimer = new Timer () { + Interval = 4000 }; + BufferTimer.Elapsed += delegate (object o, ElapsedEventArgs args) { + CheckForChanges (); + }; + + FetchTimer.Start (); BufferTimer.Start (); // Add everything that changed // since SparkleShare was stopped - AddCommitAndPush (); - SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Nothing going on..."); + SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Idling..."); } + + + public string GetDomain (string url) + { + + string domain; + + domain = url.Substring (RemoteOriginUrl.IndexOf ("@") + 1); + if (domain.IndexOf (":") > -1) + domain = domain.Substring (0, domain.IndexOf (":")); + else + domain = domain.Substring (0, domain.IndexOf ("/")); + + return domain; + + } + + + // Gets hash of the current commit + public string GetCurrentHash () + { + + string current_hash; + + Process process = new Process (); + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.UseShellExecute = false; + process.StartInfo.FileName = "git"; + process.StartInfo.Arguments = "rev-list --max-count=1 HEAD"; + process.Start (); + + current_hash = process.StandardOutput.ReadToEnd ().Trim (); + + return current_hash; + + } + + + // Gets the user's name, example: "User Name" + public string GetUserName () + { + + string user_name; + + Process process = new Process (); + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.UseShellExecute = false; + process.StartInfo.FileName = "git"; + process.StartInfo.Arguments = "config --get user.name"; + process.Start (); + + user_name = process.StandardOutput.ReadToEnd ().Trim (); + + if (user_name.Equals ("")) { + + UnixUserInfo unix_user_info = new UnixUserInfo (UnixEnvironment.UserName); + + if (unix_user_info.RealName.Equals ("")) + user_name = "???"; + else + user_name = unix_user_info.RealName; + + } + + return user_name; + + } + + + // Gets the user's email, example: "person@gnome.org" + public string GetUserEmail () + { + + string user_email; + + Process process = new Process (); + process.StartInfo.UseShellExecute = false; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.FileName = "git"; + process.StartInfo.Arguments = "config --get user.email"; + process.Start (); + user_email = process.StandardOutput.ReadToEnd ().Trim (); + + return user_email; + + } + + + // Gets the url of the remote repo, example: "ssh://git@git.gnome.org/project" + public string GetRemoteOriginUrl () + { + + string remote_origin_url; + + Process process = new Process (); + process.StartInfo.UseShellExecute = false; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.FileName = "git"; + process.StartInfo.Arguments = "config --get remote.origin.url"; + process.Start (); + + remote_origin_url = process.StandardOutput.ReadToEnd ().Trim (); + + return remote_origin_url; + + } + + + private void CheckForChanges () + { + + SparkleHelpers.DebugInfo ("Buffer", "[" + Name + "] Checking for changes."); + + lock (ChangeLock) { + + if (HasChanged) { + + SparkleHelpers.DebugInfo ("Buffer", "[" + Name + "] Changes found, checking if settled."); + + DateTime now = DateTime.UtcNow; + TimeSpan changed = new TimeSpan (now.Ticks - LastChange.Ticks); + + if (changed.TotalMilliseconds > 5000) { + HasChanged = false; + SparkleHelpers.DebugInfo ("Buffer", "[" + Name + "] Changes have settled, adding files..."); + AddCommitAndPush (); + } + + } + + } + + } + // Starts a time buffer when something changes private void OnFileActivity (object o, FileSystemEventArgs args) { + WatcherChangeTypes wct = args.ChangeType; + if (!ShouldIgnore (args.Name)) { + SparkleHelpers.DebugInfo ("Event", "[" + Name + "] " + wct.ToString () + " '" + args.Name + "'"); + FetchTimer.Stop (); - lock(ChangeLock) { + + lock (ChangeLock) { LastChange = DateTime.UtcNow; HasChanged = true; } + } + } + // When there are changes we generally want to Add, Commit and Push - // so this method does them all with appropriate timers, etc switched off + // so this method does them all with appropriate timers, etc. switched off public void AddCommitAndPush () { + try { + BufferTimer.Stop (); FetchTimer.Stop (); Add (); - string Message = FormatCommitMessage (); - if (!Message.Equals ("")) { - Commit (Message); + string message = FormatCommitMessage (); + + if (!message.Equals ("")) { + Commit (message); Fetch (); Push (); -// SparkleHelpers.CheckForUnicorns (Message); + CheckForUnicorns (message); } - } - finally { + + } finally { + FetchTimer.Start (); BufferTimer.Start (); + } } - + + // Stages the made changes private void Add () { + SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Staging changes..."); + Process.StartInfo.Arguments = "add --all"; Process.Start (); Process.WaitForExit (); + SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Changes staged."); + // SparkleUI.NotificationIcon.SetSyncingState (); // SparkleUI.NotificationIcon.SetIdleState (); + } + // Commits the made changes public void Commit (string Message) { + SparkleHelpers.DebugInfo ("Commit", "[" + Name + "] " + Message); + Process.StartInfo.Arguments = "commit -m \"" + Message + "\""; Process.Start (); Process.WaitForExit (); + } + // Fetches changes from the remote repo public void Fetch () { + try { + FetchTimer.Stop (); + // SparkleUI.NotificationIcon.SetSyncingState (); + SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Fetching changes..."); + Process.StartInfo.Arguments = "fetch -v"; Process.Start (); - string Output = Process.StandardOutput.ReadToEnd ().Trim (); // TODO: This doesn't work :( Process.WaitForExit (); + + string Output = Process.StandardOutput.ReadToEnd ().Trim (); // TODO: This doesn't work :( + SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Changes fetched."); + if (!Output.Contains ("up to date")) Rebase (); + // SparkleUI.NotificationIcon.SetIdleState (); - } - finally { + + } finally { + FetchTimer.Start (); + } + } // Merges the fetched changes @@ -235,47 +367,50 @@ namespace SparkleShare { Watcher.EnableRaisingEvents = false; SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Rebasing changes..."); + Process.StartInfo.Arguments = "rebase origin"; Process.WaitForExit (); Process.Start (); + SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Changes rebased."); - string Output = Process.StandardOutput.ReadToEnd ().Trim (); + + string output = Process.StandardOutput.ReadToEnd ().Trim (); // Show notification if there are updates - if (!Output.Contains ("up to date")) { + if (!output.Contains ("up to date")) { - if (Output.Contains ("Failed to merge")) { + if (output.Contains ("Failed to merge")) { SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Resolving conflict..."); Process.StartInfo.Arguments = "status"; Process.WaitForExit (); Process.Start (); - Output = Process.StandardOutput.ReadToEnd ().Trim (); + output = Process.StandardOutput.ReadToEnd ().Trim (); + string [] lines = Regex.Split (output, "\n"); - foreach (string Line in Regex.Split (Output, "\n")) { + foreach (string line in lines) { - if (Line.Contains ("needs merge")) { + if (line.Contains ("needs merge")) { - string ProblemFileName = Line.Substring (Line.IndexOf (": needs merge")); + string problem_file_name = line.Substring (line.IndexOf (": needs merge")); - Process.StartInfo.Arguments = "checkout --ours " + ProblemFileName; + Process.StartInfo.Arguments = "checkout --ours " + problem_file_name; Process.WaitForExit (); Process.Start (); - DateTime DateTime = new DateTime (); string TimeStamp = DateTime.Now.ToString ("H:mm d MMM yyyy"); - File.Move (ProblemFileName, - ProblemFileName + " (" + UserName + ", " + TimeStamp + ")"); + File.Move (problem_file_name, + problem_file_name + " (" + UserName + ", " + TimeStamp + ")"); Process.StartInfo.Arguments - = "checkout --theirs " + ProblemFileName; + = "checkout --theirs " + problem_file_name; Process.WaitForExit (); Process.Start (); - string ConflictTitle = "A mid-air collision happened!\n"; - string ConflictSubtext = "Don't worry, SparkleShare made\na copy of the conflicting files."; + string conflict_title = "A mid-air collision happened!\n"; + string conflict_subtext = "Don't worry, SparkleShare made\na copy of the conflicting files."; // SparkleBubble ConflictBubble = // new SparkleBubble(_(ConflictTitle), _(ConflictSubtext)); @@ -291,7 +426,9 @@ namespace SparkleShare { Process.StartInfo.Arguments = "rebase --continue"; Process.WaitForExit (); Process.Start (); + SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Conflict resolved."); + Push (); Fetch (); @@ -345,34 +482,52 @@ namespace SparkleShare { } Watcher.EnableRaisingEvents = true; - SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Nothing going on..."); + SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Idling..."); } // Pushes the changes to the remote repo public void Push () { + SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Pushing changes..."); + Process.StartInfo.Arguments = "push"; Process.Start (); Process.WaitForExit (); + SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Changes pushed."); + // SparkleUI.NotificationIcon.SetIdleState (); + } - // Ignores Repos, dotfiles, swap files and the like. - private bool ShouldIgnore (string FileName) { - if (FileName.Substring (0, 1).Equals (".") || - FileName.Contains (".lock") || - FileName.Contains (".git") || - FileName.Contains ("/.") || - Directory.Exists (LocalPath + FileName)) + + // Ignores repos, dotfiles, swap files and the like. + private bool ShouldIgnore (string file_name) { + + if (file_name.Substring (0, 1).Equals (".") || + file_name.Contains (".lock") || + file_name.Contains (".git") || + file_name.Contains ("/.") || + Directory.Exists (LocalPath + file_name)) { + return true; // Yes, ignore it. - else if (FileName.Length > 3 && FileName.Substring (FileName.Length - 4).Equals (".swp")) - return true; - else return false; + + } else if (file_name.Length > 3 && + file_name.Substring (file_name.Length - 4).Equals (".swp")) { + + return true; // Yes, ignore it. + + } else { + + return false; + + } + } + // Creates a pretty commit message based on what has changed private string FormatCommitMessage () { @@ -390,70 +545,70 @@ namespace SparkleShare { Process.Start (); string Output = Process.StandardOutput.ReadToEnd (); - foreach (string Line in Regex.Split (Output, "\n")) { - if (Line.IndexOf ("new file:") > -1) + foreach (string line in Regex.Split (Output, "\n")) { + if (line.IndexOf ("new file:") > -1) FilesAdded++; - if (Line.IndexOf ("modified:") > -1) + if (line.IndexOf ("modified:") > -1) FilesEdited++; - if (Line.IndexOf ("renamed:") > -1) + if (line.IndexOf ("renamed:") > -1) FilesRenamed++; - if (Line.IndexOf ("deleted:") > -1) + if (line.IndexOf ("deleted:") > -1) FilesDeleted++; } - foreach (string Line in Regex.Split (Output, "\n")) { + foreach (string line in Regex.Split (Output, "\n")) { // Format message for when files are added, // example: "added 'file' and 3 more." - if (Line.IndexOf ("new file:") > -1 && !DoneAddCommit) { + if (line.IndexOf ("new file:") > -1 && !DoneAddCommit) { DoneAddCommit = true; if (FilesAdded > 1) return "added ‘" + - Line.Replace ("#\tnew file:", "").Trim () + + line.Replace ("#\tnew file:", "").Trim () + "’\nand " + (FilesAdded - 1) + " more."; else return "added ‘" + - Line.Replace ("#\tnew file:", "").Trim () + "’."; + line.Replace ("#\tnew file:", "").Trim () + "’."; } // Format message for when files are edited, // example: "edited 'file'." - if (Line.IndexOf ("modified:") > -1 && !DoneEditCommit) { + if (line.IndexOf ("modified:") > -1 && !DoneEditCommit) { DoneEditCommit = true; if (FilesEdited > 1) return "edited ‘" + - Line.Replace ("#\tmodified:", "").Trim () + + line.Replace ("#\tmodified:", "").Trim () + "’\nand " + (FilesEdited - 1) + " more."; else return "edited ‘" + - Line.Replace ("#\tmodified:", "").Trim () + "’."; + line.Replace ("#\tmodified:", "").Trim () + "’."; } // Format message for when files are edited, // example: "deleted 'file'." - if (Line.IndexOf ("deleted:") > -1 && !DoneDeleteCommit) { + if (line.IndexOf ("deleted:") > -1 && !DoneDeleteCommit) { DoneDeleteCommit = true; if (FilesDeleted > 1) return "deleted ‘" + - Line.Replace ("#\tdeleted:", "").Trim () + + line.Replace ("#\tdeleted:", "").Trim () + "’\nand " + (FilesDeleted - 1) + " more."; else return "deleted ‘" + - Line.Replace ("#\tdeleted:", "").Trim () + "’."; + line.Replace ("#\tdeleted:", "").Trim () + "’."; } // Format message for when files are renamed, // example: "renamed 'file' to 'new name'." - if (Line.IndexOf ("renamed:") > -1 && !DoneRenameCommit) { + if (line.IndexOf ("renamed:") > -1 && !DoneRenameCommit) { DoneDeleteCommit = true; if (FilesRenamed > 1) return "renamed ‘" + - Line.Replace ("#\trenamed:", "").Trim ().Replace + line.Replace ("#\trenamed:", "").Trim ().Replace (" -> ", "’ to ‘") + "’ and " + (FilesDeleted - 1) + " more."; else return "renamed ‘" + - Line.Replace ("#\trenamed:", "").Trim ().Replace + line.Replace ("#\trenamed:", "").Trim ().Replace (" -> ", "’ to ‘") + "’."; } diff --git a/SparkleShare/SparkleWindow.cs b/SparkleShare/SparkleWindow.cs index ffed1261..090aaa0b 100644 --- a/SparkleShare/SparkleWindow.cs +++ b/SparkleShare/SparkleWindow.cs @@ -42,7 +42,7 @@ namespace SparkleShare { { SparkleRepo = sparkle_repo; - SetSizeRequest (640, 480); + SetSizeRequest (550, 720); SetPosition (WindowPosition.Center); BorderWidth = 12; @@ -54,9 +54,10 @@ namespace SparkleShare { LayoutVertical.PackStart (CreateEventLog (), true, true, 0); - HButtonBox dialog_buttons = new HButtonBox (); - dialog_buttons.Layout = ButtonBoxStyle.Edge; - dialog_buttons.BorderWidth = 0; + HButtonBox dialog_buttons = new HButtonBox { + Layout = ButtonBoxStyle.Edge, + BorderWidth = 0 + }; Button open_folder_button = new Button (_("Open Folder")); open_folder_button.Clicked += delegate (object o, EventArgs args) { @@ -79,7 +80,7 @@ namespace SparkleShare { LayoutVertical.PackStart (dialog_buttons, false, false, 0); Add (LayoutVertical); - + } @@ -104,7 +105,7 @@ namespace SparkleShare { process.StartInfo.UseShellExecute = false; process.StartInfo.WorkingDirectory = SparkleRepo.LocalPath; process.StartInfo.FileName = "git"; - process.StartInfo.Arguments = "log --format=\"%at☃%an☃%ae☃%s\" -25"; + process.StartInfo.Arguments = "log --format=\"%at☃%an☃%ae☃%s\" -50"; string output = ""; @@ -185,7 +186,12 @@ namespace SparkleShare { } - Label date_label = new Label (); + Label date_label = new Label ("") { + UseMarkup = true, + Xalign = 0, + Xpad = 9, + Ypad = 9 + }; DateTime today = DateTime.Now; DateTime yesterday = DateTime.Now.AddDays (-1); @@ -194,39 +200,32 @@ namespace SparkleShare { today.Month == activity_day.DateTime.Month && today.Year == activity_day.DateTime.Year) { - date_label.Text = "Today"; + date_label.Markup = "Today"; } else if (yesterday.Day == activity_day.DateTime.Day && yesterday.Month == activity_day.DateTime.Month && yesterday.Year == activity_day.DateTime.Year) { - date_label.Text = "Yesterday"; + date_label.Markup = "Yesterday"; } else { - date_label.Text = "" + activity_day.DateTime.ToString ("ddd MMM d, yyyy") + ""; + date_label.Markup = "" + activity_day.DateTime.ToString ("ddd MMM d, yyyy") + ""; } - date_label.UseMarkup = true; - date_label.Xalign = 0; - date_label.Xpad = 9; - date_label.Ypad = 9; - layout_vertical.PackStart (date_label, true, true, 0); - IconView icon_view = new IconView (list_store); - - icon_view.PixbufColumn = 0; - icon_view.MarkupColumn = 1; - - icon_view.Orientation = Orientation.Horizontal; - icon_view.ItemWidth = 550; - icon_view.Spacing = 9; + IconView icon_view = new IconView (list_store) { + ItemWidth = 480, + MarkupColumn = 1, + Orientation = Orientation.Horizontal, + PixbufColumn = 0, + Spacing = 9 + }; layout_vertical.PackStart (icon_view); - } ScrolledWindow = new ScrolledWindow (); From 7befe3ee43ab3530e4c39251758e495f6c532853 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Wed, 21 Jul 2010 00:01:09 +0100 Subject: [PATCH 24/37] Add a broken 'Added' event in sparklerepo --- SparkleShare/SparkleIntro.cs | 39 +++++++++++++++++++----------------- SparkleShare/SparkleRepo.cs | 20 ++++++++++++++++++ SparkleShare/SparkleUI.cs | 31 ++++++++++++++++++++++++---- 3 files changed, 68 insertions(+), 22 deletions(-) diff --git a/SparkleShare/SparkleIntro.cs b/SparkleShare/SparkleIntro.cs index bfdf350a..67662723 100644 --- a/SparkleShare/SparkleIntro.cs +++ b/SparkleShare/SparkleIntro.cs @@ -69,34 +69,37 @@ namespace SparkleShare { VBox layout_vertical = new VBox (false, 0); - Label introduction = new Label ("Welcome to SparkleShare!"); - introduction.UseMarkup = true; - introduction.Xalign = 0; + Label introduction = new Label ("Welcome to SparkleShare!") { + UseMarkup = true, + Xalign = 0 + }; Label information = new Label ("Before we can create a SparkleShare folder on this " + - "computer, we need a few bits of information from you."); - information.Xalign = 0; - information.Wrap = true; - + "computer, we need a few bits of information from you.") { + Xalign = 0, + Wrap = true + }; - Entry name_entry = new Entry (""); - Label name_label = new Label (_("Full Name:")); - UnixUserInfo unix_user_info = new UnixUserInfo (UnixEnvironment.UserName); - name_entry.Text = unix_user_info.RealName; - name_label.UseMarkup = true; - name_label.Xalign = 0; + Label name_label = new Label (_("Full Name:")) { + Text = unix_user_info.RealName, + UseMarkup = true, + Xalign = 0 + }; + Entry name_entry = new Entry (""); - Table table = new Table (6, 2, true); - table.RowSpacing = 6; + Table table = new Table (6, 2, true) { + RowSpacing = 6 + }; Entry email_entry = new Entry (""); - Label email_label = new Label (_("Email:")); - email_label.UseMarkup = true; - email_label.Xalign = 0; + Label email_label = new Label (_("Email:")) { + UseMarkup = true, + Xalign = 0 + }; Entry server_entry = new Entry ("ssh://gitorious.org/sparkleshare"); Label server_label = new Label (_("Folder Address:")); diff --git a/SparkleShare/SparkleRepo.cs b/SparkleShare/SparkleRepo.cs index 701b493b..b4598e3c 100644 --- a/SparkleShare/SparkleRepo.cs +++ b/SparkleShare/SparkleRepo.cs @@ -45,6 +45,11 @@ namespace SparkleShare { public string UserEmail; public string UserName; + + public delegate void AddedEventHandler (object o, SparkleEventArgs args); + public event AddedEventHandler Added; + + public static string _ (string s) { return Catalog.GetString (s); @@ -311,6 +316,10 @@ namespace SparkleShare { // SparkleUI.NotificationIcon.SetSyncingState (); // SparkleUI.NotificationIcon.SetIdleState (); + SparkleEventArgs args = new SparkleEventArgs ("add test"); + if (Added != null) + Added (this, args); + } @@ -637,4 +646,15 @@ namespace SparkleShare { } + + + + + + + } + + + + diff --git a/SparkleShare/SparkleUI.cs b/SparkleShare/SparkleUI.cs index 3293cfa5..f17d6434 100644 --- a/SparkleShare/SparkleUI.cs +++ b/SparkleShare/SparkleUI.cs @@ -24,13 +24,12 @@ using System.IO; namespace SparkleShare { - public class SparkleUI - { + public class SparkleUI { private Process Process; // Short alias for the translations - public static string _ (string s) + public static string _(string s) { return Catalog.GetString (s); } @@ -217,6 +216,10 @@ namespace SparkleShare { } + public void Test (object o, SparkleEventArgs args) { + Console.WriteLine ("AAAAAAAAAAAAAAAAAA"); + } + public void UpdateRepositories () { @@ -251,11 +254,31 @@ namespace SparkleShare { } + SparkleRepo a = TmpRepos [0]; + a.Added += new SparkleRepo.AddedEventHandler (Test); + + SparkleShare.Repositories = new SparkleRepo [FolderCount]; Array.Copy (TmpRepos, SparkleShare.Repositories, FolderCount); - } } +} + public class SparkleEventArgs : System.EventArgs { + + private string message; + + + public SparkleEventArgs (string s) + { + this.message = s; + } + + public string Message () + { + return message; + } + +} } From 5949b860816315b22538d381f17d7543bfd13de7 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Wed, 21 Jul 2010 00:03:27 +0100 Subject: [PATCH 25/37] Fix error --- SparkleShare/SparkleIntro.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/SparkleShare/SparkleIntro.cs b/SparkleShare/SparkleIntro.cs index 67662723..59c875b7 100644 --- a/SparkleShare/SparkleIntro.cs +++ b/SparkleShare/SparkleIntro.cs @@ -102,11 +102,11 @@ namespace SparkleShare { }; Entry server_entry = new Entry ("ssh://gitorious.org/sparkleshare"); - Label server_label = new Label (_("Folder Address:")); - server_label.UseMarkup = true; - server_label.Xalign = 0; - server_label.Sensitive = false; - server_entry.Sensitive = false; + Label server_label = new Label (_("Folder Address:")) { + UseMarkup = true, + Xalign = 0, + Sensitive = false + }; CheckButton check_button = new CheckButton ("I already have an existing folder on a SparkleShare server"); check_button.Clicked += delegate { From d826a1663491b25fff146afdd88be551a8eab21c Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Wed, 21 Jul 2010 01:06:34 +0100 Subject: [PATCH 26/37] Code cleanup --- SparkleShare/SparkleIntro.cs | 102 +++++++++++++++++++++-------------- 1 file changed, 63 insertions(+), 39 deletions(-) diff --git a/SparkleShare/SparkleIntro.cs b/SparkleShare/SparkleIntro.cs index 59c875b7..2e886a82 100644 --- a/SparkleShare/SparkleIntro.cs +++ b/SparkleShare/SparkleIntro.cs @@ -67,28 +67,32 @@ namespace SparkleShare { VBox wrapper = new VBox (false, 0); - VBox layout_vertical = new VBox (false, 0); + VBox layout_vertical = new VBox (false, 0) { + BorderWidth = 30 + }; - Label introduction = new Label ("Welcome to SparkleShare!") { + Label introduction = new Label ("" + + _("Welcome to SparkleShare!") + + "") { UseMarkup = true, Xalign = 0 }; - Label information = new Label ("Before we can create a SparkleShare folder on this " + - "computer, we need a few bits of information from you.") { + Label information = new Label (_("Before we can create a SparkleShare folder on this " + + "computer, we need a few bits of information from you.")) { Xalign = 0, Wrap = true }; UnixUserInfo unix_user_info = new UnixUserInfo (UnixEnvironment.UserName); - Label name_label = new Label (_("Full Name:")) { - Text = unix_user_info.RealName, + Label name_label = new Label ("" + _("Full Name:") + "") { UseMarkup = true, Xalign = 0 }; - Entry name_entry = new Entry (""); + Entry name_entry = new Entry (unix_user_info.RealName); + Table table = new Table (6, 2, true) { RowSpacing = 6 @@ -96,29 +100,42 @@ namespace SparkleShare { Entry email_entry = new Entry (""); - Label email_label = new Label (_("Email:")) { + Label email_label = new Label ("" + _("Email:") + "") { UseMarkup = true, Xalign = 0 }; - Entry server_entry = new Entry ("ssh://gitorious.org/sparkleshare"); - Label server_label = new Label (_("Folder Address:")) { + Entry server_entry = new Entry ("ssh://gitorious.org/sparkleshare") { + Sensitive = false + }; + + Label server_label = new Label ("" + _("Folder Address:") + "") { UseMarkup = true, Xalign = 0, Sensitive = false }; - CheckButton check_button = new CheckButton ("I already have an existing folder on a SparkleShare server"); + CheckButton check_button; + check_button = new CheckButton (_("I already have access to an existing " + + "folder on a SparkleShare server")); + check_button.Clicked += delegate { + if (check_button.Active) { + server_label.Sensitive = true; server_entry.Sensitive = true; server_entry.HasFocus = true; + } else { + server_label.Sensitive = false; server_entry.Sensitive = false; + } + ShowAll (); + }; table.Attach (name_label, 0, 1, 0, 1); @@ -129,19 +146,27 @@ namespace SparkleShare { table.Attach (server_label, 0, 1, 4, 5); table.Attach (server_entry, 1, 2, 4, 5); - HButtonBox controls = new HButtonBox (); - controls.Layout = ButtonBoxStyle.End; + HButtonBox controls = new HButtonBox () { + BorderWidth = 12, + Layout = ButtonBoxStyle.End + }; Button done_button = new Button (_("Next")); done_button.Clicked += delegate (object o, EventArgs args) { + done_button.Remove (done_button.Child); + HBox hbox = new HBox (); + hbox.Add (new SparkleSpinner ()); - hbox.Add (new Label ("Configuring…")); + hbox.Add (new Label (_("Configuring…"))); + done_button.Add (hbox); + done_button.Sensitive = false; - table.Sensitive = false; + table.Sensitive = false; + done_button.ShowAll (); string user_name = name_entry.Text; @@ -150,11 +175,9 @@ namespace SparkleShare { string config_file_path = SparkleHelpers.CombineMore (SparklePaths.SparkleConfigPath, ".gitconfig"); TextWriter writer = new StreamWriter (config_file_path); - writer.WriteLine ("[user]\n" + - "\tname = " + user_name + "\n" + + "\tname = " + user_name + "\n" + "\temail = " + user_email + "\n"); - writer.Close (); SparkleHelpers.DebugInfo ("Config", "Created '" + config_file_path + "'"); @@ -171,8 +194,7 @@ namespace SparkleShare { layout_vertical.PackStart (table, false, false, 0); wrapper.PackStart (layout_vertical, true, true, 0); - layout_vertical.BorderWidth = 30; - controls.BorderWidth = 12; + wrapper.PackStart (controls, false, true, 0); layout_horizontal.PackStart (wrapper, true, true, 0); @@ -195,37 +217,39 @@ namespace SparkleShare { VBox wrapper = new VBox (false, 0); - VBox layout_vertical = new VBox (false, 0); - layout_vertical.BorderWidth = 30; + VBox layout_vertical = new VBox (false, 0) { + BorderWidth = 30 + }; - Label introduction; - introduction = new Label ("SparkleShare is ready to go!"); + Label introduction = new Label ("" + + _("SparkleShare is ready to go!") + + "") { + UseMarkup = true, + Xalign = 0 + }; - introduction.UseMarkup = true; - introduction.Xalign = 0; + Label information = new Label (_("Now you can start accepting invitations from others. " + + "Just click on invitations you get by email and " + + "we will take care of the rest.")) { + UseMarkup = true, + Wrap = true, + Xalign = 0 + }; - Label information; - information = new Label ("You can now start accepting invitations from others. " + - "Just click on invitations you get by email and " + - "we will take care of the rest."); - - information.UseMarkup = true; - information.Wrap = true; - information.Xalign = 0; HBox link_wrapper = new HBox (false, 0); LinkButton link = new LinkButton ("http://www.sparkleshare.org/", - _("Learn how to host your own SparkleSpace")); - + _("Learn how to host your own SparkleServer")); link_wrapper.PackStart (link, false, false, 0); layout_vertical.PackStart (introduction, false, false, 0); layout_vertical.PackStart (information, false, false, 21); layout_vertical.PackStart (link_wrapper, false, false, 0); - HButtonBox controls = new HButtonBox (); - controls.Layout = ButtonBoxStyle.End; - controls.BorderWidth = 12; + HButtonBox controls = new HButtonBox () { + Layout = ButtonBoxStyle.End, + BorderWidth = 12 + }; Button finish_button = new Button (_("Finish")); From 78bf3dbf8419bf18ee0c160c5908c3358d4a80ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Jerna=C5=9B?= Date: Wed, 21 Jul 2010 20:53:45 +0200 Subject: [PATCH 27/37] Add initial i18n support to the Nautilus plugin. --- SparkleShare/Nautilus/Makefile.am | 2 +- ...nsion.py => sparkleshare-nautilus-extension.py.in} | 11 ++++++++--- configure.ac | 1 + po/POTFILES.in | 1 + 4 files changed, 11 insertions(+), 4 deletions(-) rename SparkleShare/Nautilus/{sparkleshare-nautilus-extension.py => sparkleshare-nautilus-extension.py.in} (92%) diff --git a/SparkleShare/Nautilus/Makefile.am b/SparkleShare/Nautilus/Makefile.am index a821d5e6..feb9a115 100644 --- a/SparkleShare/Nautilus/Makefile.am +++ b/SparkleShare/Nautilus/Makefile.am @@ -6,5 +6,5 @@ NAUTILUS_PYTHON_INSTALL_DIR=$(subst $(NAUTILUS_LIBDIR),${libdir},$(NAUTILUS_PYTH extensiondir = $(NAUTILUS_PYTHON_INSTALL_DIR) extension_SCRIPTS = $(addprefix $(srcdir)/, $(SOURCES)) else -EXTRA_DIST = $(SOURCES) +EXTRA_DIST = $(SOURCES) sparkleshare-nautilus-extension.py.in endif diff --git a/SparkleShare/Nautilus/sparkleshare-nautilus-extension.py b/SparkleShare/Nautilus/sparkleshare-nautilus-extension.py.in similarity index 92% rename from SparkleShare/Nautilus/sparkleshare-nautilus-extension.py rename to SparkleShare/Nautilus/sparkleshare-nautilus-extension.py.in index 56885fb1..d2883389 100644 --- a/SparkleShare/Nautilus/sparkleshare-nautilus-extension.py +++ b/SparkleShare/Nautilus/sparkleshare-nautilus-extension.py.in @@ -23,6 +23,11 @@ import nautilus SPARKLESHARE_PATH = os.path.join (os.path.expanduser ('~'), "SparkleShare") +import gettext +gettext.bindtextdomain('sparkleshare', '@prefix@/share/locale') +gettext.textdomain('sparkleshare') +_ = gettext.gettext + class SparkleShareExtension (nautilus.MenuProvider): @@ -100,8 +105,8 @@ class SparkleShareExtension (nautilus.MenuProvider): commit_hashes [i] = line.strip ("\n") i += 1 - earlier_version_menu_item = nautilus.MenuItem ("Nautilus::OpenOlderVersion", "Get Earlier Version", - "Make a copy of an earlier version in this folder") + earlier_version_menu_item = nautilus.MenuItem ("Nautilus::OpenOlderVersion", _("Get Earlier Version"), + _("Make a copy of an earlier version in this folder")) submenu = nautilus.Menu () i = 0 @@ -114,7 +119,7 @@ class SparkleShareExtension (nautilus.MenuProvider): menu_item = nautilus.MenuItem ("Nautilus::Version" + epochs [i], timestamp + "\t" + username, - "Select to get a copy of this version") + _("Select to get a copy of this version")) menu_item.connect ("activate", self.checkout_version, file_reference, commit_hashes [i], username, time.localtime (float (epochs [i]))) diff --git a/configure.ac b/configure.ac index 21b2da33..cf934f3c 100644 --- a/configure.ac +++ b/configure.ac @@ -95,6 +95,7 @@ SparkleShare/Defines.cs SparkleShare/AssemblyInfo.cs SparkleShare/Makefile SparkleShare/Nautilus/Makefile +SparkleShare/Nautilus/sparkleshare-nautilus-extension.py po/Makefile.in Makefile ]) diff --git a/po/POTFILES.in b/po/POTFILES.in index d99b5d04..846e6940 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -15,3 +15,4 @@ SparkleShare/SparkleSpinner.cs SparkleShare/SparkleStatusIcon.cs SparkleShare/SparkleUI.cs SparkleShare/SparkleWindow.cs +SparkleShare/Nautilus/sparkleshare-nautilus-extension.py.in From 64659572e67686257a43ad09a84cab2c1467845d Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Wed, 21 Jul 2010 20:39:28 +0100 Subject: [PATCH 28/37] code cleanup --- SparkleShare/SparkleBubble.cs | 20 +-- SparkleShare/SparkleIntro.cs | 242 ++++++++++++++++++---------------- SparkleShare/SparkleRepo.cs | 11 +- SparkleShare/SparkleUI.cs | 208 +++++++++++++++-------------- 4 files changed, 257 insertions(+), 224 deletions(-) diff --git a/SparkleShare/SparkleBubble.cs b/SparkleShare/SparkleBubble.cs index 0558ced5..db577b54 100644 --- a/SparkleShare/SparkleBubble.cs +++ b/SparkleShare/SparkleBubble.cs @@ -22,22 +22,26 @@ namespace SparkleShare { public class SparkleBubble : Notification { - public SparkleBubble (string Title, string Subtext) : base (Title, Subtext) { + public SparkleBubble (string title, string subtext) : base (title, subtext) + { - Timeout = 4500; - Urgency = Urgency.Low; IconName = "folder-sparkleshare"; + Timeout = 4500; + Urgency = Urgency.Low; + AttachToStatusIcon (SparkleUI.NotificationIcon); } + // Checks whether the system allows adding buttons to a notification, - // prevents error messages in e.g. Ubuntu. - new public void AddAction (string Action, string Label, ActionHandler Handler) + // prevents error messages in Ubuntu. + new public void AddAction (string action, string label, ActionHandler handler) { - bool CanHaveButtons = (System.Array.IndexOf (Notifications.Global.Capabilities, "actions") > -1); - if (CanHaveButtons) - base.AddAction(Action, Label, Handler); + + if (System.Array.IndexOf (Notifications.Global.Capabilities, "actions") > -1) + base.AddAction (action, label, handler); + } } diff --git a/SparkleShare/SparkleIntro.cs b/SparkleShare/SparkleIntro.cs index 2e886a82..e234b8c1 100644 --- a/SparkleShare/SparkleIntro.cs +++ b/SparkleShare/SparkleIntro.cs @@ -29,32 +29,32 @@ namespace SparkleShare { public class SparkleIntro : Window { + public Entry NameEntry; + public Entry EmailEntry; + public Entry ServerEntry; + // Short alias for the translations public static string _ (string s) { return Catalog.GetString (s); } + public SparkleIntro () : base ("") { -// using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider()) - // { - // File.WriteAllText ("PublicKeyOnly.xml", rsa.ToXmlString (false)); - // File.WriteAllText ("PublicPrivate.xml", rsa.ToXmlString (true)); - //} - - BorderWidth = 0; - SetSizeRequest (640, 400); - Resizable = false; - IconName = "folder-sparkleshare"; - + BorderWidth = 0; + IconName = "folder-sparkleshare"; + Resizable = false; WindowPosition = WindowPosition.Center; - + + SetSizeRequest (640, 400); + ShowStepOne (); } - + + public void ShowStepOne () { @@ -63,139 +63,127 @@ namespace SparkleShare { // TODO: Fix the path Image side_splash = new Image ("/home/hbons/github/SparkleShare/data/side-splash.png"); - layout_horizontal.PackStart (side_splash, false, false, 0); + VBox wrapper = new VBox (false, 0); - VBox wrapper = new VBox (false, 0); - - VBox layout_vertical = new VBox (false, 0) { - BorderWidth = 30 - }; - - Label introduction = new Label ("" + - _("Welcome to SparkleShare!") + - "") { - UseMarkup = true, - Xalign = 0 - }; - - Label information = new Label (_("Before we can create a SparkleShare folder on this " + - "computer, we need a few bits of information from you.")) { - Xalign = 0, - Wrap = true - }; - - UnixUserInfo unix_user_info = new UnixUserInfo (UnixEnvironment.UserName); - - Label name_label = new Label ("" + _("Full Name:") + "") { - UseMarkup = true, - Xalign = 0 + VBox layout_vertical = new VBox (false, 0) { + BorderWidth = 30 }; + + Label introduction = new Label ("" + + _("Welcome to SparkleShare!") + + "") { + UseMarkup = true, + Xalign = 0 + }; + + Label information = new Label (_("Before we can create a SparkleShare folder on this " + + "computer, we need a few bits of information from you.")) { + Xalign = 0, + Wrap = true + }; - Entry name_entry = new Entry (unix_user_info.RealName); + UnixUserInfo unix_user_info = new UnixUserInfo (UnixEnvironment.UserName); + + Label name_label = new Label ("" + _("Full Name:") + "") { + UseMarkup = true, + Xalign = 0 + }; + + NameEntry = new Entry (unix_user_info.RealName); - Table table = new Table (6, 2, true) { - RowSpacing = 6 - }; - + Table table = new Table (6, 2, true) { + RowSpacing = 6 + }; - Entry email_entry = new Entry (""); - Label email_label = new Label ("" + _("Email:") + "") { - UseMarkup = true, - Xalign = 0 - }; + EmailEntry = new Entry (""); + Label email_label = new Label ("" + _("Email:") + "") { + UseMarkup = true, + Xalign = 0 + }; - Entry server_entry = new Entry ("ssh://gitorious.org/sparkleshare") { - Sensitive = false - }; + Entry server_entry = new Entry ("ssh://gitorious.org/sparkleshare") { + Sensitive = false + }; - Label server_label = new Label ("" + _("Folder Address:") + "") { - UseMarkup = true, - Xalign = 0, - Sensitive = false - }; + Label server_label = new Label ("" + _("Folder Address:") + "") { + UseMarkup = true, + Xalign = 0, + Sensitive = false + }; - CheckButton check_button; - check_button = new CheckButton (_("I already have access to an existing " + - "folder on a SparkleShare server")); + CheckButton check_button; + check_button = new CheckButton (_("I already subscribed to an existing " + + "folder on a SparkleShare server")); - check_button.Clicked += delegate { + check_button.Clicked += delegate { - if (check_button.Active) { + if (check_button.Active) { - server_label.Sensitive = true; - server_entry.Sensitive = true; - server_entry.HasFocus = true; + server_label.Sensitive = true; + server_entry.Sensitive = true; + server_entry.HasFocus = true; - } else { + } else { - server_label.Sensitive = false; - server_entry.Sensitive = false; + server_label.Sensitive = false; + server_entry.Sensitive = false; - } + } - ShowAll (); + ShowAll (); - }; + }; - table.Attach (name_label, 0, 1, 0, 1); - table.Attach (name_entry, 1, 2, 0, 1); - table.Attach (email_label, 0, 1, 1, 2); - table.Attach (email_entry, 1, 2, 1, 2); - table.Attach (check_button, 0, 2, 3, 4); - table.Attach (server_label, 0, 1, 4, 5); - table.Attach (server_entry, 1, 2, 4, 5); + table.Attach (name_label, 0, 1, 0, 1); + table.Attach (NameEntry, 1, 2, 0, 1); + table.Attach (email_label, 0, 1, 1, 2); + table.Attach (EmailEntry, 1, 2, 1, 2); + table.Attach (check_button, 0, 2, 3, 4); + table.Attach (server_label, 0, 1, 4, 5); + table.Attach (server_entry, 1, 2, 4, 5); - HButtonBox controls = new HButtonBox () { - BorderWidth = 12, - Layout = ButtonBoxStyle.End - }; + HButtonBox controls = new HButtonBox () { + BorderWidth = 12, + Layout = ButtonBoxStyle.End + }; - Button done_button = new Button (_("Next")); + Button done_button = new Button (_("Next")); - done_button.Clicked += delegate (object o, EventArgs args) { + done_button.Clicked += delegate (object o, EventArgs args) { - done_button.Remove (done_button.Child); + done_button.Remove (done_button.Child); - HBox hbox = new HBox (); + HBox hbox = new HBox (); - hbox.Add (new SparkleSpinner ()); - hbox.Add (new Label (_("Configuring…"))); + hbox.Add (new SparkleSpinner ()); + hbox.Add (new Label (_("Configuring…"))); - done_button.Add (hbox); + done_button.Add (hbox); - done_button.Sensitive = false; - table.Sensitive = false; + done_button.Sensitive = false; + table.Sensitive = false; - done_button.ShowAll (); + done_button.ShowAll (); - string user_name = name_entry.Text; - string user_email = email_entry.Text; + Configure (); - string config_file_path = SparkleHelpers.CombineMore (SparklePaths.SparkleConfigPath, ".gitconfig"); + ShowStepTwo (); - TextWriter writer = new StreamWriter (config_file_path); - writer.WriteLine ("[user]\n" + - "\tname = " + user_name + "\n" + - "\temail = " + user_email + "\n"); - writer.Close (); - - SparkleHelpers.DebugInfo ("Config", "Created '" + config_file_path + "'"); - - ShowStepTwo (); - - }; + }; - controls.Add (done_button); + controls.Add (done_button); - layout_vertical.PackStart (introduction, false, false, 0); - layout_vertical.PackStart (information, false, false, 21); - layout_vertical.PackStart (new Label (""), false, false, 0); - layout_vertical.PackStart (table, false, false, 0); + layout_vertical.PackStart (introduction, false, false, 0); + layout_vertical.PackStart (information, false, false, 21); + layout_vertical.PackStart (new Label (""), false, false, 0); + layout_vertical.PackStart (table, false, false, 0); - wrapper.PackStart (layout_vertical, true, true, 0); + wrapper.PackStart (layout_vertical, true, true, 0); - wrapper.PackStart (controls, false, true, 0); + wrapper.PackStart (controls, false, true, 0); + + layout_horizontal.PackStart (side_splash, false, false, 0); layout_horizontal.PackStart (wrapper, true, true, 0); Add (layout_horizontal); @@ -203,7 +191,8 @@ namespace SparkleShare { ShowAll (); } - + + public void ShowStepTwo () { @@ -240,6 +229,7 @@ namespace SparkleShare { HBox link_wrapper = new HBox (false, 0); LinkButton link = new LinkButton ("http://www.sparkleshare.org/", _("Learn how to host your own SparkleServer")); + link_wrapper.PackStart (link, false, false, 0); layout_vertical.PackStart (introduction, false, false, 0); @@ -270,6 +260,32 @@ namespace SparkleShare { } + + // Configure SparkleShare with the user's information + public void Configure () + { + + string config_file_path = SparkleHelpers.CombineMore (SparklePaths.SparkleConfigPath, ".gitconfig"); + + TextWriter writer = new StreamWriter (config_file_path); + writer.WriteLine ("[user]\n" + + "\tname = " + NameEntry.Text + "\n" + + "\temail = " + EmailEntry.Text + "\n"); + writer.Close (); + + GenerateKeyPair (); + + SparkleHelpers.DebugInfo ("Config", "Created '" + config_file_path + "'"); + + } + + + // Generates an RSA keypair to identify this system + public void GenerateKeyPair () + { + + } + } } diff --git a/SparkleShare/SparkleRepo.cs b/SparkleShare/SparkleRepo.cs index b4598e3c..c48a0a1e 100644 --- a/SparkleShare/SparkleRepo.cs +++ b/SparkleShare/SparkleRepo.cs @@ -647,11 +647,16 @@ namespace SparkleShare { } + public class SparkleEventArgs : System.EventArgs { + + public string Message; + public SparkleEventArgs (string s) + { + Message = s; + } - - - + } } diff --git a/SparkleShare/SparkleUI.cs b/SparkleShare/SparkleUI.cs index f17d6434..e2f2deed 100644 --- a/SparkleShare/SparkleUI.cs +++ b/SparkleShare/SparkleUI.cs @@ -46,98 +46,23 @@ namespace SparkleShare { string SparklePath = SparklePaths.SparklePath; - - // Create .desktop entry in autostart folder to - // start SparkleShare on each login - switch (SparklePlatform.Name) { - case "GNOME": - - string autostart_path = SparkleHelpers.CombineMore (SparklePaths.HomePath, ".config", "autostart"); - string desktopfile_path = SparkleHelpers.CombineMore (autostart_path, "sparkleshare.desktop"); - - if (!File.Exists (desktopfile_path)) { - - if (!Directory.Exists (autostart_path)) - Directory.CreateDirectory (autostart_path); - - TextWriter writer = new StreamWriter (desktopfile_path); - - writer.WriteLine ("[Desktop Entry]\n" + - "Type=Application\n" + - "Name=SparkleShare\n" + - "Exec=sparkleshare start\n" + - "Icon=folder-sparkleshare\n" + - "Terminal=false\n" + - "X-GNOME-Autostart-enabled=true"); - - writer.Close (); - - // Give the launcher the right permissions so it can be launched by the user - Syscall.chmod (desktopfile_path, FilePermissions.S_IRWXU); - - SparkleHelpers.DebugInfo ("Config", "Created '" + desktopfile_path + "'"); - - } - - break; - - } - - - // Create 'SparkleShare' folder in the user's home folder - // if it's not there already - if (!Directory.Exists (SparklePath)) { - - Directory.CreateDirectory (SparklePath); - SparkleHelpers.DebugInfo ("Config", "Created '" + SparklePath + "'"); - - // Add a special icon to the SparkleShare folder - switch (SparklePlatform.Name) { - case "GNOME": - - Process.StartInfo.FileName = "gvfs-set-attribute"; - Process.StartInfo.Arguments = SparklePath + " metadata::custom-icon " + - "file:///usr/share/icons/hicolor/48x48/places/" + - "folder-sparkleshare.png"; - Process.Start (); - - break; - - } - - // Add the SparkleShare folder to the bookmarks - switch (SparklePlatform.Name) { - case "GNOME": - - string BookmarksFileName = - Path.Combine (SparklePaths.HomePath, ".gtk-bookmarks"); - - if (File.Exists (BookmarksFileName)) { - TextWriter TextWriter = File.AppendText (BookmarksFileName); - TextWriter.WriteLine ("file://" + SparklePath + " SparkleShare"); - TextWriter.Close (); - } - - break; - - } - - } + EnableSystemAutostart (); + CreateSparkleShareFolder (); // Create a directory to store temporary files in if (!Directory.Exists (SparklePaths.SparkleTmpPath)) Directory.CreateDirectory (SparklePaths.SparkleTmpPath); - if (!HideUI) - NotificationIcon = new SparkleStatusIcon (); - UpdateRepositories (); // Don't create the window and status // icon when --disable-gui was given if (!HideUI) { + SparkleIntro intro = new SparkleIntro (); intro.ShowAll (); + + NotificationIcon = new SparkleStatusIcon (); // Show a notification if there are no folders yet if (SparkleShare.Repositories.Length == 0) { @@ -214,12 +139,110 @@ namespace SparkleShare { File.Create (NotifySettingFile); } + + // Creates .desktop entry in autostart folder to + // start SparkleShare automnatically at login + public void EnableSystemAutostart () + { + + switch (SparklePlatform.Name) { + + case "GNOME": + + string autostart_path = SparkleHelpers.CombineMore (SparklePaths.HomePath, ".config", "autostart"); + string desktopfile_path = SparkleHelpers.CombineMore (autostart_path, "sparkleshare.desktop"); + + if (!File.Exists (desktopfile_path)) { + + if (!Directory.Exists (autostart_path)) + Directory.CreateDirectory (autostart_path); + + TextWriter writer = new StreamWriter (desktopfile_path); + + writer.WriteLine ("[Desktop Entry]\n" + + "Type=Application\n" + + "Name=SparkleShare\n" + + "Exec=sparkleshare start\n" + + "Icon=folder-sparkleshare\n" + + "Terminal=false\n" + + "X-GNOME-Autostart-enabled=true"); + + writer.Close (); + + // Give the launcher the right permissions so it can be launched by the user + Syscall.chmod (desktopfile_path, FilePermissions.S_IRWXU); + + SparkleHelpers.DebugInfo ("Config", "Created '" + desktopfile_path + "'"); + + } + + break; + + } + + } + + + public void AddToBookmarks () + { + + // Add the SparkleShare folder to the bookmarks + switch (SparklePlatform.Name) { + + case "GNOME": + + string bookmarks_file_name = Path.Combine (SparklePaths.HomePath, ".gtk-bookmarks"); + + if (File.Exists (bookmarks_file_name)) { + TextWriter writer = File.AppendText (bookmarks_file_name); + writer.WriteLine ("file://" + SparklePaths.SparklePath + " SparkleShare"); + writer.Close (); + } + + break; + + } + + } + + + // Creates the 'SparkleShare' folder in the user's home folder if + // it's not already there + public void CreateSparkleShareFolder () + { + + if (!Directory.Exists (SparklePaths.SparklePath)) { + + Directory.CreateDirectory (SparklePaths.SparklePath); + SparkleHelpers.DebugInfo ("Config", "Created '" + SparklePaths.SparklePath + "'"); + + // Add a special icon to the SparkleShare folder + switch (SparklePlatform.Name) { + + case "GNOME": + + Process.StartInfo.FileName = "gvfs-set-attribute"; + Process.StartInfo.Arguments = SparklePaths.SparklePath + " metadata::custom-icon " + + "file:///usr/share/icons/hicolor/48x48/places/" + + "folder-sparkleshare.png"; + Process.Start (); + + break; + + } + + AddToBookmarks (); + + } + + } public void Test (object o, SparkleEventArgs args) { Console.WriteLine ("AAAAAAAAAAAAAAAAAA"); } + public void UpdateRepositories () { @@ -228,12 +251,12 @@ namespace SparkleShare { SparkleRepo [] TmpRepos = new SparkleRepo [Directory.GetDirectories (SparklePath).Length]; int FolderCount = 0; - foreach (string Folder in Directory.GetDirectories (SparklePath)) { + foreach (string folder in Directory.GetDirectories (SparklePath)) { // Check if the folder is a git repo - if (Directory.Exists (SparkleHelpers.CombineMore (Folder, ".git"))) { + if (Directory.Exists (SparkleHelpers.CombineMore (folder, ".git"))) { - TmpRepos [FolderCount] = new SparkleRepo (Folder); + TmpRepos [FolderCount] = new SparkleRepo (folder); FolderCount++; // TODO: emblems don't show up in nautilus @@ -242,7 +265,7 @@ namespace SparkleShare { case "GNOME": Process.StartInfo.FileName = "gvfs-set-attribute"; - Process.StartInfo.Arguments = "-t string \"" + Folder + + Process.StartInfo.Arguments = "-t string \"" + folder + "\" metadata::emblems [synced]"; Process.Start (); @@ -255,7 +278,7 @@ namespace SparkleShare { } SparkleRepo a = TmpRepos [0]; - a.Added += new SparkleRepo.AddedEventHandler (Test); + a.Added += Test; SparkleShare.Repositories = new SparkleRepo [FolderCount]; @@ -264,21 +287,6 @@ namespace SparkleShare { } } - public class SparkleEventArgs : System.EventArgs { - - private string message; - - public SparkleEventArgs (string s) - { - this.message = s; - } - - public string Message () - { - return message; - } - -} } From 7faaa4debff7e27efd3091f7b03844bac2056b61 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Wed, 21 Jul 2010 23:31:51 +0100 Subject: [PATCH 29/37] Continue work on SparkleIntro --- SparkleShare/SparkleHelpers.cs | 7 +- SparkleShare/SparkleIntro.cs | 161 ++++++++++++++++++++++++++------- 2 files changed, 132 insertions(+), 36 deletions(-) diff --git a/SparkleShare/SparkleHelpers.cs b/SparkleShare/SparkleHelpers.cs index 88f53e49..a76dffee 100644 --- a/SparkleShare/SparkleHelpers.cs +++ b/SparkleShare/SparkleHelpers.cs @@ -38,7 +38,7 @@ namespace SparkleShare { Directory.CreateDirectory (AvatarPath); SparkleHelpers.DebugInfo ("Config", "Created '" + AvatarPath + "'"); } - + string AvatarFilePath = CombineMore (AvatarPath, "avatar-" + Email); if (File.Exists (AvatarFilePath)) @@ -115,7 +115,10 @@ namespace SparkleShare { if (ShowDebugInfo) { DateTime DateTime = new DateTime (); string TimeStamp = DateTime.Now.ToString ("HH:mm:ss"); - Console.WriteLine ("[" + TimeStamp + "]" + "[" + Type + "] " + Message); + if (Message.StartsWith ("[")) + Console.WriteLine ("[" + TimeStamp + "]" + "[" + Type + "]" + Message); + else + Console.WriteLine ("[" + TimeStamp + "]" + "[" + Type + "] " + Message); } } diff --git a/SparkleShare/SparkleIntro.cs b/SparkleShare/SparkleIntro.cs index e234b8c1..da9a52d4 100644 --- a/SparkleShare/SparkleIntro.cs +++ b/SparkleShare/SparkleIntro.cs @@ -2,36 +2,34 @@ // Copyright (C) 2010 Hylke Bons // // 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 +// it under the terms of the GNU General private 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. +// GNU General private License for more details. // -// You should have received a copy of the GNU General Public License +// You should have received a copy of the GNU General private License // along with this program. If not, see . using Gtk; using Mono.Unix; using SparkleShare; using System; -using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Text.RegularExpressions; -using System.Timers; -using System.Security.Cryptography; namespace SparkleShare { public class SparkleIntro : Window { - public Entry NameEntry; - public Entry EmailEntry; - public Entry ServerEntry; + private Entry NameEntry; + private Entry EmailEntry; + private Entry ServerEntry; + private Button NextButton; // Short alias for the translations public static string _ (string s) @@ -55,7 +53,7 @@ namespace SparkleShare { } - public void ShowStepOne () + private void ShowStepOne () { HBox layout_horizontal = new HBox (false, 6); @@ -82,6 +80,10 @@ namespace SparkleShare { Wrap = true }; + Table table = new Table (6, 2, true) { + RowSpacing = 6 + }; + UnixUserInfo unix_user_info = new UnixUserInfo (UnixEnvironment.UserName); Label name_label = new Label ("" + _("Full Name:") + "") { @@ -90,19 +92,22 @@ namespace SparkleShare { }; NameEntry = new Entry (unix_user_info.RealName); + NameEntry.Changed += delegate { + CheckFields (); + }; - - Table table = new Table (6, 2, true) { - RowSpacing = 6 - }; - EmailEntry = new Entry (""); + EmailEntry = new Entry (GetUserEmail ()); + EmailEntry.Changed += delegate { + CheckFields (); + }; + Label email_label = new Label ("" + _("Email:") + "") { UseMarkup = true, Xalign = 0 }; - Entry server_entry = new Entry ("ssh://gitorious.org/sparkleshare") { + ServerEntry = new Entry ("ssh://gitorious.org/sparkleshare") { Sensitive = false }; @@ -113,7 +118,7 @@ namespace SparkleShare { }; CheckButton check_button; - check_button = new CheckButton (_("I already subscribed to an existing " + + check_button = new CheckButton (_("I'm already subscribed to an existing " + "folder on a SparkleShare server")); check_button.Clicked += delegate { @@ -121,13 +126,13 @@ namespace SparkleShare { if (check_button.Active) { server_label.Sensitive = true; - server_entry.Sensitive = true; - server_entry.HasFocus = true; + ServerEntry.Sensitive = true; + ServerEntry.HasFocus = true; } else { server_label.Sensitive = false; - server_entry.Sensitive = false; + ServerEntry.Sensitive = false; } @@ -141,38 +146,38 @@ namespace SparkleShare { table.Attach (EmailEntry, 1, 2, 1, 2); table.Attach (check_button, 0, 2, 3, 4); table.Attach (server_label, 0, 1, 4, 5); - table.Attach (server_entry, 1, 2, 4, 5); + table.Attach (ServerEntry, 1, 2, 4, 5); HButtonBox controls = new HButtonBox () { BorderWidth = 12, Layout = ButtonBoxStyle.End }; - Button done_button = new Button (_("Next")); + NextButton = new Button (_("Next")) { + Sensitive = false + }; - done_button.Clicked += delegate (object o, EventArgs args) { + NextButton.Clicked += delegate (object o, EventArgs args) { - done_button.Remove (done_button.Child); + NextButton.Remove (NextButton.Child); HBox hbox = new HBox (); hbox.Add (new SparkleSpinner ()); hbox.Add (new Label (_("Configuring…"))); - done_button.Add (hbox); + NextButton.Add (hbox); - done_button.Sensitive = false; + NextButton.Sensitive = false; table.Sensitive = false; - done_button.ShowAll (); + NextButton.ShowAll (); Configure (); - ShowStepTwo (); - }; - controls.Add (done_button); + controls.Add (NextButton); layout_vertical.PackStart (introduction, false, false, 0); layout_vertical.PackStart (information, false, false, 21); @@ -188,12 +193,14 @@ namespace SparkleShare { Add (layout_horizontal); + CheckFields (); + ShowAll (); } - public void ShowStepTwo () + private void ShowStepTwo () { Remove (Child); @@ -261,8 +268,25 @@ namespace SparkleShare { } + private void CheckFields () + { + + if (NameEntry.Text.Length > 0 && + IsValidEmail (EmailEntry.Text)) { + + NextButton.Sensitive = true; + + } else { + + NextButton.Sensitive = false; + + } + + } + + // Configure SparkleShare with the user's information - public void Configure () + private void Configure () { string config_file_path = SparkleHelpers.CombineMore (SparklePaths.SparkleConfigPath, ".gitconfig"); @@ -275,15 +299,84 @@ namespace SparkleShare { GenerateKeyPair (); + ShowStepTwo (); + SparkleHelpers.DebugInfo ("Config", "Created '" + config_file_path + "'"); } - // Generates an RSA keypair to identify this system - public void GenerateKeyPair () + private string GetUserEmail () { + string user_email = ""; + string keys_path = System.IO.Path.Combine (SparklePaths.HomePath, ".ssh"); + + if (!Directory.Exists (keys_path)) + return ""; + + foreach (string file_path in Directory.GetFiles (keys_path)) { + + string file_name = System.IO.Path.GetFileName (file_path); + + if (file_name.StartsWith ("sparkleshare.") && file_name.EndsWith (".key")) { + + user_email = file_name.Substring (file_name.IndexOf (".") + 1); + user_email = user_email.Substring (0, user_email.LastIndexOf (".")); + + return user_email; + + } + + } + + return ""; + + } + + + // Generates and installs an RSA keypair to identify this system + private void GenerateKeyPair () + { + + string user_email = EmailEntry.Text; + string keys_path = System.IO.Path.Combine (SparklePaths.HomePath, ".ssh"); + string key_file_name = "sparkleshare." + user_email + ".key"; + + Process process = new Process () { + EnableRaisingEvents = true + }; + + if (!Directory.Exists (keys_path)) + Directory.CreateDirectory (keys_path); + + if (!File.Exists (key_file_name)) { + + process.StartInfo.WorkingDirectory = keys_path; + process.StartInfo.UseShellExecute = false; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.FileName = "ssh-keygen"; + process.StartInfo.Arguments = "-t rsa -P " + user_email + " -f " + key_file_name; + + process.WaitForExit (); + process.Start (); + + process.Exited += delegate { + SparkleHelpers.DebugInfo ("Config", "Created key '" + key_file_name + "'"); + SparkleHelpers.DebugInfo ("Config", "Created key '" + key_file_name + ".pub'"); + }; + + } + + } + + + private bool IsValidEmail(string email) + { + + Regex regex = new Regex(@"^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$", RegexOptions.IgnoreCase); + return regex.IsMatch (email); + } } From ddda81532752236f0c150de4e278027a8341c153 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Thu, 22 Jul 2010 00:05:31 +0100 Subject: [PATCH 30/37] Add some comments --- SparkleShare/SparkleIntro.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/SparkleShare/SparkleIntro.cs b/SparkleShare/SparkleIntro.cs index da9a52d4..bf3a3bb3 100644 --- a/SparkleShare/SparkleIntro.cs +++ b/SparkleShare/SparkleIntro.cs @@ -118,8 +118,8 @@ namespace SparkleShare { }; CheckButton check_button; - check_button = new CheckButton (_("I'm already subscribed to an existing " + - "folder on a SparkleShare server")); + check_button = new CheckButton (_("I'm already subscribed to a " + + "folder on a SparkleServer")); check_button.Clicked += delegate { @@ -268,6 +268,8 @@ namespace SparkleShare { } + // Enables or disables the "Next" button depending on the + // entries filled in by the user private void CheckFields () { @@ -285,7 +287,7 @@ namespace SparkleShare { } - // Configure SparkleShare with the user's information + // Configures SparkleShare with the user's information private void Configure () { @@ -297,15 +299,16 @@ namespace SparkleShare { "\temail = " + EmailEntry.Text + "\n"); writer.Close (); + SparkleHelpers.DebugInfo ("Config", "Created '" + config_file_path + "'"); + GenerateKeyPair (); ShowStepTwo (); - SparkleHelpers.DebugInfo ("Config", "Created '" + config_file_path + "'"); - } + // Gets the email address if the user alreasy has a SparkleShare key installed private string GetUserEmail () { @@ -371,6 +374,7 @@ namespace SparkleShare { } + // Checks to see if an email address is valid private bool IsValidEmail(string email) { From f0a540f204cb9655f5ccc42f528ff79347bd636f Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Thu, 22 Jul 2010 00:17:20 +0100 Subject: [PATCH 31/37] Add some more events to SparkleRepo --- SparkleShare/SparkleRepo.cs | 246 ++++++++++++++++++++---------------- 1 file changed, 139 insertions(+), 107 deletions(-) diff --git a/SparkleShare/SparkleRepo.cs b/SparkleShare/SparkleRepo.cs index c48a0a1e..3500596a 100644 --- a/SparkleShare/SparkleRepo.cs +++ b/SparkleShare/SparkleRepo.cs @@ -49,6 +49,15 @@ namespace SparkleShare { public delegate void AddedEventHandler (object o, SparkleEventArgs args); public event AddedEventHandler Added; + public delegate void CommitedEventHandler (object o, SparkleEventArgs args); + public event CommitedEventHandler Commited; + + public delegate void PushedEventHandler (object o, SparkleEventArgs args); + public event PushedEventHandler Pushed; + + public delegate void FetchedEventHandler (object o, SparkleEventArgs args); + public event FetchedEventHandler Fetched; + public static string _ (string s) { @@ -74,6 +83,7 @@ namespace SparkleShare { CurrentHash = GetCurrentHash (); Domain = GetDomain (RemoteOriginUrl); + // Watch the repository's folder Watcher = new FileSystemWatcher (LocalPath) { IncludeSubdirectories = true, @@ -85,6 +95,7 @@ namespace SparkleShare { Watcher.Created += new FileSystemEventHandler (OnFileActivity); Watcher.Deleted += new FileSystemEventHandler (OnFileActivity); + // Fetch remote changes every 20 seconds FetchTimer = new Timer () { Interval = 20000 @@ -94,6 +105,7 @@ namespace SparkleShare { Fetch (); }; + // Keep a buffer that checks if there are changes and // whether they have settled BufferTimer = new Timer () { @@ -104,9 +116,11 @@ namespace SparkleShare { CheckForChanges (); }; + FetchTimer.Start (); BufferTimer.Start (); + // Add everything that changed // since SparkleShare was stopped AddCommitAndPush (); @@ -114,112 +128,6 @@ namespace SparkleShare { SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Idling..."); } - - - public string GetDomain (string url) - { - - string domain; - - domain = url.Substring (RemoteOriginUrl.IndexOf ("@") + 1); - if (domain.IndexOf (":") > -1) - domain = domain.Substring (0, domain.IndexOf (":")); - else - domain = domain.Substring (0, domain.IndexOf ("/")); - - return domain; - - } - - - // Gets hash of the current commit - public string GetCurrentHash () - { - - string current_hash; - - Process process = new Process (); - process.StartInfo.RedirectStandardOutput = true; - process.StartInfo.UseShellExecute = false; - process.StartInfo.FileName = "git"; - process.StartInfo.Arguments = "rev-list --max-count=1 HEAD"; - process.Start (); - - current_hash = process.StandardOutput.ReadToEnd ().Trim (); - - return current_hash; - - } - - - // Gets the user's name, example: "User Name" - public string GetUserName () - { - - string user_name; - - Process process = new Process (); - process.StartInfo.RedirectStandardOutput = true; - process.StartInfo.UseShellExecute = false; - process.StartInfo.FileName = "git"; - process.StartInfo.Arguments = "config --get user.name"; - process.Start (); - - user_name = process.StandardOutput.ReadToEnd ().Trim (); - - if (user_name.Equals ("")) { - - UnixUserInfo unix_user_info = new UnixUserInfo (UnixEnvironment.UserName); - - if (unix_user_info.RealName.Equals ("")) - user_name = "???"; - else - user_name = unix_user_info.RealName; - - } - - return user_name; - - } - - - // Gets the user's email, example: "person@gnome.org" - public string GetUserEmail () - { - - string user_email; - - Process process = new Process (); - process.StartInfo.UseShellExecute = false; - process.StartInfo.RedirectStandardOutput = true; - process.StartInfo.FileName = "git"; - process.StartInfo.Arguments = "config --get user.email"; - process.Start (); - user_email = process.StandardOutput.ReadToEnd ().Trim (); - - return user_email; - - } - - - // Gets the url of the remote repo, example: "ssh://git@git.gnome.org/project" - public string GetRemoteOriginUrl () - { - - string remote_origin_url; - - Process process = new Process (); - process.StartInfo.UseShellExecute = false; - process.StartInfo.RedirectStandardOutput = true; - process.StartInfo.FileName = "git"; - process.StartInfo.Arguments = "config --get remote.origin.url"; - process.Start (); - - remote_origin_url = process.StandardOutput.ReadToEnd ().Trim (); - - return remote_origin_url; - - } private void CheckForChanges () @@ -316,7 +224,8 @@ namespace SparkleShare { // SparkleUI.NotificationIcon.SetSyncingState (); // SparkleUI.NotificationIcon.SetIdleState (); - SparkleEventArgs args = new SparkleEventArgs ("add test"); + SparkleEventArgs args = new SparkleEventArgs ("Added"); + if (Added != null) Added (this, args); @@ -333,6 +242,11 @@ namespace SparkleShare { Process.Start (); Process.WaitForExit (); + SparkleEventArgs args = new SparkleEventArgs ("Commited"); + + if (Commited != null) + Commited (this, args); + } @@ -356,6 +270,11 @@ namespace SparkleShare { SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Changes fetched."); + SparkleEventArgs args = new SparkleEventArgs ("Fetched"); + + if (Fetched != null) + Fetched (this, args); + if (!Output.Contains ("up to date")) Rebase (); @@ -369,6 +288,7 @@ namespace SparkleShare { } + // Merges the fetched changes public void Rebase () { @@ -495,6 +415,7 @@ namespace SparkleShare { } + // Pushes the changes to the remote repo public void Push () { @@ -507,6 +428,11 @@ namespace SparkleShare { SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Changes pushed."); + SparkleEventArgs args = new SparkleEventArgs ("Pushed"); + + if (Pushed != null) + Pushed (this, args); + // SparkleUI.NotificationIcon.SetIdleState (); } @@ -537,6 +463,112 @@ namespace SparkleShare { } + public string GetDomain (string url) + { + + string domain; + + domain = url.Substring (RemoteOriginUrl.IndexOf ("@") + 1); + if (domain.IndexOf (":") > -1) + domain = domain.Substring (0, domain.IndexOf (":")); + else + domain = domain.Substring (0, domain.IndexOf ("/")); + + return domain; + + } + + + // Gets hash of the current commit + public string GetCurrentHash () + { + + string current_hash; + + Process process = new Process (); + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.UseShellExecute = false; + process.StartInfo.FileName = "git"; + process.StartInfo.Arguments = "rev-list --max-count=1 HEAD"; + process.Start (); + + current_hash = process.StandardOutput.ReadToEnd ().Trim (); + + return current_hash; + + } + + + // Gets the user's name, example: "User Name" + public string GetUserName () + { + + string user_name; + + Process process = new Process (); + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.UseShellExecute = false; + process.StartInfo.FileName = "git"; + process.StartInfo.Arguments = "config --get user.name"; + process.Start (); + + user_name = process.StandardOutput.ReadToEnd ().Trim (); + + if (user_name.Equals ("")) { + + UnixUserInfo unix_user_info = new UnixUserInfo (UnixEnvironment.UserName); + + if (unix_user_info.RealName.Equals ("")) + user_name = "???"; + else + user_name = unix_user_info.RealName; + + } + + return user_name; + + } + + + // Gets the user's email, example: "person@gnome.org" + public string GetUserEmail () + { + + string user_email; + + Process process = new Process (); + process.StartInfo.UseShellExecute = false; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.FileName = "git"; + process.StartInfo.Arguments = "config --get user.email"; + process.Start (); + user_email = process.StandardOutput.ReadToEnd ().Trim (); + + return user_email; + + } + + + // Gets the url of the remote repo, example: "ssh://git@git.gnome.org/project" + public string GetRemoteOriginUrl () + { + + string remote_origin_url; + + Process process = new Process (); + process.StartInfo.UseShellExecute = false; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.FileName = "git"; + process.StartInfo.Arguments = "config --get remote.origin.url"; + process.Start (); + + remote_origin_url = process.StandardOutput.ReadToEnd ().Trim (); + + return remote_origin_url; + + } + + // Creates a pretty commit message based on what has changed private string FormatCommitMessage () { From 10c2862beface1d3db3b49c11e572dfc3325d1be Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Thu, 22 Jul 2010 22:10:38 +0100 Subject: [PATCH 32/37] fix some bugs and crashes --- SparkleShare/Defines.cs.in | 18 ++++++------ SparkleShare/SparkleIntro.cs | 5 ++-- SparkleShare/SparkleRepo.cs | 41 +++++++++++++++++---------- SparkleShare/SparkleShare.cs | 1 - SparkleShare/SparkleStatusIcon.cs | 36 +++++++++++++++++------ SparkleShare/SparkleUI.cs | 47 ++++++++++++++++--------------- SparkleShare/SparkleWindow.cs | 10 ++++--- 7 files changed, 96 insertions(+), 62 deletions(-) diff --git a/SparkleShare/Defines.cs.in b/SparkleShare/Defines.cs.in index 567c76fd..b5c4dcfc 100644 --- a/SparkleShare/Defines.cs.in +++ b/SparkleShare/Defines.cs.in @@ -12,17 +12,19 @@ // 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 . +// along with this program. If not, see . using System; -namespace SparkleShare -{ - public class Defines - { - public const string VERSION = "@VERSION@"; - public const string LOCALE_DIR = "@prefix@/share/locale"; +namespace SparkleShare { + + public class Defines { + + public const string VERSION = "@VERSION@"; + public const string LOCALE_DIR = "@prefix@/share/locale"; public const string GETTEXT_PACKAGE = "@GETTEXT_PACKAGE@"; - public const string PREFIX = "@prefix@"; + public const string PREFIX = "@prefix@"; + } + } diff --git a/SparkleShare/SparkleIntro.cs b/SparkleShare/SparkleIntro.cs index bf3a3bb3..f107d873 100644 --- a/SparkleShare/SparkleIntro.cs +++ b/SparkleShare/SparkleIntro.cs @@ -12,7 +12,7 @@ // GNU General private License for more details. // // You should have received a copy of the GNU General private License -// along with this program. If not, see . +// along with this program. If not, see . using Gtk; using Mono.Unix; @@ -268,7 +268,7 @@ namespace SparkleShare { } - // Enables or disables the "Next" button depending on the + // Enables or disables the 'Next' button depending on the // entries filled in by the user private void CheckFields () { @@ -361,7 +361,6 @@ namespace SparkleShare { process.StartInfo.FileName = "ssh-keygen"; process.StartInfo.Arguments = "-t rsa -P " + user_email + " -f " + key_file_name; - process.WaitForExit (); process.Start (); process.Exited += delegate { diff --git a/SparkleShare/SparkleRepo.cs b/SparkleShare/SparkleRepo.cs index 3500596a..3dfac41d 100644 --- a/SparkleShare/SparkleRepo.cs +++ b/SparkleShare/SparkleRepo.cs @@ -12,7 +12,7 @@ // 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 . +// along with this program. If not, see . using Gtk; using Mono.Unix; @@ -25,7 +25,7 @@ using System.Timers; namespace SparkleShare { - // SparkleRepo class holds repository information and timers + // Holds repository information and timers public class SparkleRepo { @@ -55,8 +55,14 @@ namespace SparkleShare { public delegate void PushedEventHandler (object o, SparkleEventArgs args); public event PushedEventHandler Pushed; - public delegate void FetchedEventHandler (object o, SparkleEventArgs args); - public event FetchedEventHandler Fetched; + public delegate void FetchingStartedEventHandler (object o, SparkleEventArgs args); + public event FetchingStartedEventHandler FetchingStarted; + + public delegate void FetchingFinishedEventHandler (object o, SparkleEventArgs args); + public event FetchingFinishedEventHandler FetchingFinished; + + public delegate void NewCommitEventHandler (object o, SparkleEventArgs args); + public event NewCommitEventHandler NewCommit; public static string _ (string s) @@ -221,9 +227,6 @@ namespace SparkleShare { SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Changes staged."); -// SparkleUI.NotificationIcon.SetSyncingState (); -// SparkleUI.NotificationIcon.SetIdleState (); - SparkleEventArgs args = new SparkleEventArgs ("Added"); if (Added != null) @@ -258,7 +261,11 @@ namespace SparkleShare { FetchTimer.Stop (); -// SparkleUI.NotificationIcon.SetSyncingState (); + SparkleEventArgs args; + args = new SparkleEventArgs ("FetchingStarted"); + + if (FetchingStarted != null) + FetchingStarted (this, args); SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Fetching changes..."); @@ -270,16 +277,15 @@ namespace SparkleShare { SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Changes fetched."); - SparkleEventArgs args = new SparkleEventArgs ("Fetched"); + args = new SparkleEventArgs ("FetchingFinished"); - if (Fetched != null) - Fetched (this, args); + if (FetchingFinished != null) + FetchingFinished (this, args); + // Rebase if there are changes if (!Output.Contains ("up to date")) Rebase (); -// SparkleUI.NotificationIcon.SetIdleState (); - } finally { FetchTimer.Start (); @@ -297,7 +303,7 @@ namespace SparkleShare { SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Rebasing changes..."); - Process.StartInfo.Arguments = "rebase origin"; + Process.StartInfo.Arguments = "rebase -v master"; Process.WaitForExit (); Process.Start (); @@ -339,7 +345,7 @@ namespace SparkleShare { Process.Start (); string conflict_title = "A mid-air collision happened!\n"; - string conflict_subtext = "Don't worry, SparkleShare made\na copy of the conflicting files."; + string conflict_subtext = "Don't worry, SparkleShare made\na copy of each conflicting file."; // SparkleBubble ConflictBubble = // new SparkleBubble(_(ConflictTitle), _(ConflictSubtext)); @@ -385,6 +391,11 @@ namespace SparkleShare { SparkleHelpers.DebugInfo ("Notification", "[" + Name + "] Showing message..."); + SparkleEventArgs args = new SparkleEventArgs ("NewCommit"); + + if (NewCommit != null) + NewCommit (this, args); + // SparkleBubble StuffChangedBubble = new SparkleBubble (LastCommitUserName, LastCommitMessage); // StuffChangedBubble.Icon = SparkleHelpers.GetAvatar (LastCommitEmail, 32); diff --git a/SparkleShare/SparkleShare.cs b/SparkleShare/SparkleShare.cs index 350f0bc6..edade95e 100644 --- a/SparkleShare/SparkleShare.cs +++ b/SparkleShare/SparkleShare.cs @@ -30,7 +30,6 @@ namespace SparkleShare { return Catalog.GetString (s); } - public static SparkleRepo [] Repositories; public static SparkleUI SparkleUI; public static void Main (string [] args) diff --git a/SparkleShare/SparkleStatusIcon.cs b/SparkleShare/SparkleStatusIcon.cs index d65f7e2d..f35d151f 100644 --- a/SparkleShare/SparkleStatusIcon.cs +++ b/SparkleShare/SparkleStatusIcon.cs @@ -12,7 +12,7 @@ // 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 . +// along with this program. If not, see . using Gtk; using Mono.Unix; @@ -30,6 +30,8 @@ namespace SparkleShare { private Timer Timer; private int SyncingState; + public int SyncingReposCount; + // Short alias for the translations public static string _ (string s) { return Catalog.GetString (s); @@ -49,6 +51,8 @@ namespace SparkleShare { Timer = new Timer (); Activate += ShowMenu; + SyncingReposCount = 0; + // 0 = Everything up to date // 1 = Syncing in progress // -1 = Error syncing @@ -82,7 +86,7 @@ namespace SparkleShare { Menu.Add (StatusMenuItem); Menu.Add (new SeparatorMenuItem ()); - Gtk.Action FolderAction = new Gtk.Action ("", "SparkleShare"); + Gtk.Action FolderAction = new Gtk.Action ("", "SparkleShare Folder"); FolderAction.IconName = "folder-sparkleshare"; FolderAction.IsImportant = true; FolderAction.Activated += delegate { @@ -101,10 +105,10 @@ namespace SparkleShare { Menu.Add (FolderAction.CreateMenuItem ()); Gtk.Action [] FolderItems = - new Gtk.Action [SparkleShare.Repositories.Length]; + new Gtk.Action [SparkleUI.Repositories.Length]; int i = 0; - foreach (SparkleRepo SparkleRepo in SparkleShare.Repositories) { + foreach (SparkleRepo SparkleRepo in SparkleUI.Repositories) { FolderItems [i] = new Gtk.Action ("", SparkleRepo.Name); FolderItems [i].IconName = "folder"; FolderItems [i].IsImportant = true; @@ -113,7 +117,7 @@ namespace SparkleShare { i++; } - MenuItem AddItem = new MenuItem (_("Add a Remote Folder…")); + MenuItem AddItem = new MenuItem (_("Add Remote Folder…")); AddItem.Activated += delegate { SparkleDialog SparkleDialog = new SparkleDialog (""); SparkleDialog.ShowAll (); @@ -139,7 +143,7 @@ namespace SparkleShare { } }; - MenuItem AboutItem = new MenuItem (_("Visit Website")); + MenuItem AboutItem = new MenuItem (_("About")); AboutItem.Activated += delegate { Process Process = new Process (); switch (SparklePlatform.Name) { @@ -163,10 +167,22 @@ namespace SparkleShare { Menu.Popup (null, null, SetPosition, 0, Global.CurrentEventTime); } + + public void UpdateState () + { + + if (SyncingReposCount > 0) + SetSyncingState (); + else + SetIdleState (); + + } + + public void SetIdleState () { Timer.Stop (); - Pixbuf = SparkleHelpers.GetIcon ("folder-sparkleshare", 24); + IconName = "folder-sparkleshare"; SyncingState = 0; } @@ -176,9 +192,11 @@ namespace SparkleShare { public void SetSyncingState () { + IconName = "view-refresh"; + SyncingState = 1; - int CycleDuration = 250; +/* int CycleDuration = 250; int CurrentStep = 0; int Size = 24; @@ -209,7 +227,7 @@ namespace SparkleShare { Pixbuf = Images [CurrentStep]; }; Timer.Start (); - +*/ } // Changes the status icon to the error icon diff --git a/SparkleShare/SparkleUI.cs b/SparkleShare/SparkleUI.cs index e2f2deed..5f31c13a 100644 --- a/SparkleShare/SparkleUI.cs +++ b/SparkleShare/SparkleUI.cs @@ -12,7 +12,7 @@ // 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 . +// along with this program. If not, see . using Gtk; using Mono.Unix; @@ -27,6 +27,7 @@ namespace SparkleShare { public class SparkleUI { private Process Process; + public static SparkleRepo [] Repositories; // Short alias for the translations public static string _(string s) @@ -64,7 +65,7 @@ namespace SparkleShare { NotificationIcon = new SparkleStatusIcon (); // Show a notification if there are no folders yet - if (SparkleShare.Repositories.Length == 0) { + if (Repositories.Length == 0) { SparkleBubble NoFoldersBubble; NoFoldersBubble = new SparkleBubble (_("Welcome to SparkleShare!"), @@ -238,11 +239,27 @@ namespace SparkleShare { } - public void Test (object o, SparkleEventArgs args) { + public void ShowNewCommitBubble (object o, SparkleEventArgs args) { Console.WriteLine ("AAAAAAAAAAAAAAAAAA"); } + public void UpdateStatusIcon (object o, SparkleEventArgs args) { + + if (args.Message.Equals ("FetchingStarted")) { + NotificationIcon.SyncingReposCount++; + NotificationIcon.UpdateState (); + } + + if (args.Message.Equals ("FetchingFinished")) { + NotificationIcon.SyncingReposCount--; + NotificationIcon.UpdateState (); + } + + } + + + // TODO: Make this a List instead of an array public void UpdateRepositories () { @@ -257,32 +274,18 @@ namespace SparkleShare { if (Directory.Exists (SparkleHelpers.CombineMore (folder, ".git"))) { TmpRepos [FolderCount] = new SparkleRepo (folder); + TmpRepos [FolderCount].NewCommit += ShowNewCommitBubble; + TmpRepos [FolderCount].FetchingStarted += UpdateStatusIcon; + TmpRepos [FolderCount].FetchingFinished += UpdateStatusIcon; FolderCount++; - // TODO: emblems don't show up in nautilus - // Attach emblems - switch (SparklePlatform.Name) { - case "GNOME": - - Process.StartInfo.FileName = "gvfs-set-attribute"; - Process.StartInfo.Arguments = "-t string \"" + folder + - "\" metadata::emblems [synced]"; - Process.Start (); - - break; - - } - } } - SparkleRepo a = TmpRepos [0]; - a.Added += Test; - - SparkleShare.Repositories = new SparkleRepo [FolderCount]; - Array.Copy (TmpRepos, SparkleShare.Repositories, FolderCount); + Repositories = new SparkleRepo [FolderCount]; + Array.Copy (TmpRepos, Repositories, FolderCount); } diff --git a/SparkleShare/SparkleWindow.cs b/SparkleShare/SparkleWindow.cs index 090aaa0b..0c90cc3d 100644 --- a/SparkleShare/SparkleWindow.cs +++ b/SparkleShare/SparkleWindow.cs @@ -99,13 +99,15 @@ namespace SparkleShare { private ScrolledWindow CreateEventLog () { + int number_of_events = 50; + Process process = new Process (); process.EnableRaisingEvents = true; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.UseShellExecute = false; process.StartInfo.WorkingDirectory = SparkleRepo.LocalPath; process.StartInfo.FileName = "git"; - process.StartInfo.Arguments = "log --format=\"%at☃%an☃%ae☃%s\" -50"; + process.StartInfo.Arguments = "log --format=\"%at☃%an☃%ae☃%s\" -" + number_of_events; string output = ""; @@ -122,7 +124,7 @@ namespace SparkleShare { List activity_days = new List (); - for (int i = 0; i < 25 && i < lines.Length; i++) { + for (int i = 0; i < number_of_events && i < lines.Length; i++) { string line = lines [i]; @@ -214,7 +216,7 @@ namespace SparkleShare { } - layout_vertical.PackStart (date_label, true, true, 0); + layout_vertical.PackStart (date_label, false, false, 0); IconView icon_view = new IconView (list_store) { ItemWidth = 480, @@ -224,7 +226,7 @@ namespace SparkleShare { Spacing = 9 }; - layout_vertical.PackStart (icon_view); + layout_vertical.PackStart (icon_view, false, false, 0); } From d9150d2d2fb45f2d2816d6283235cb06c787959d Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Fri, 23 Jul 2010 00:01:57 +0100 Subject: [PATCH 33/37] [statusicon] Update menu text depending on the syncing state --- SparkleShare/SparkleStatusIcon.cs | 104 +++++++++++++++++------------- SparkleShare/SparkleUI.cs | 4 +- 2 files changed, 62 insertions(+), 46 deletions(-) diff --git a/SparkleShare/SparkleStatusIcon.cs b/SparkleShare/SparkleStatusIcon.cs index f35d151f..e7849167 100644 --- a/SparkleShare/SparkleStatusIcon.cs +++ b/SparkleShare/SparkleStatusIcon.cs @@ -27,16 +27,38 @@ namespace SparkleShare { public class SparkleStatusIcon : StatusIcon { - private Timer Timer; - private int SyncingState; +// private Timer Timer; public int SyncingReposCount; + private Menu Menu; + private MenuItem StatusMenuItem; + private string StateText; + // Short alias for the translations public static string _ (string s) { return Catalog.GetString (s); } + + public SparkleStatusIcon () : base () + { + +// Timer = new Timer (); + + StateText = ""; + StatusMenuItem = new MenuItem (StateText); + SyncingReposCount = 0; + + CreateMenu (); + Activate += ShowMenu; + + SetIdleState (); + ShowState (); + + } + + public EventHandler CreateWindowDelegate (SparkleRepo SparkleRepo) { return delegate { @@ -45,42 +67,13 @@ namespace SparkleShare { }; } - public SparkleStatusIcon () : base () + + private void CreateMenu () { - Timer = new Timer (); - Activate += ShowMenu; + Menu = new Menu (); - SyncingReposCount = 0; - - // 0 = Everything up to date - // 1 = Syncing in progress - // -1 = Error syncing - SyncingState = 0; - - SetIdleState (); - - } - - public void ShowMenu (object o, EventArgs Args) - { - - Menu Menu = new Menu (); - - string StateText = ""; - switch (SyncingState) { - case -1: - StateText = _("Error syncing"); - break; - case 0: - StateText = _("Everything is up to date"); - break; - case 1: - StateText = _("Syncing…"); - break; - } - - MenuItem StatusMenuItem = new MenuItem (StateText); + StatusMenuItem = new MenuItem (StateText); StatusMenuItem.Sensitive = false; Menu.Add (StatusMenuItem); @@ -163,12 +156,30 @@ namespace SparkleShare { MenuItem QuitItem = new MenuItem (_("Quit")); QuitItem.Activated += Quit; Menu.Add (QuitItem); - Menu.ShowAll (); - Menu.Popup (null, null, SetPosition, 0, Global.CurrentEventTime); + } - public void UpdateState () + private void ShowMenu (object o, EventArgs args) + { + + Menu.ShowAll (); + Menu.Popup (null, null, SetPosition, 0, Global.CurrentEventTime); + + } + + + private void UpdateStatusMenuItem () + { + + Label label = (Label) StatusMenuItem.Children [0]; + label.Text = StateText; + + Menu.ShowAll (); + + } + + public void ShowState () { if (SyncingReposCount > 0) @@ -176,14 +187,18 @@ namespace SparkleShare { else SetIdleState (); + UpdateStatusMenuItem (); + } public void SetIdleState () { - Timer.Stop (); - IconName = "folder-sparkleshare"; - SyncingState = 0; +// Timer.Stop (); + + IconName = "folder-sparkleshare"; + StateText = _("Everything is up to date"); + } // Changes the status icon to the syncing animation @@ -193,8 +208,7 @@ namespace SparkleShare { { IconName = "view-refresh"; - - SyncingState = 1; + StateText = _("Syncing…"); /* int CycleDuration = 250; int CurrentStep = 0; @@ -233,8 +247,10 @@ namespace SparkleShare { // Changes the status icon to the error icon public void SetErrorState () { + IconName = "folder-sync-error"; - SyncingState = -1; + StateText = _("Error syncing"); + } public void SetPosition (Menu menu, out int x, out int y, out bool push_in) diff --git a/SparkleShare/SparkleUI.cs b/SparkleShare/SparkleUI.cs index 5f31c13a..76f6bd72 100644 --- a/SparkleShare/SparkleUI.cs +++ b/SparkleShare/SparkleUI.cs @@ -248,12 +248,12 @@ namespace SparkleShare { if (args.Message.Equals ("FetchingStarted")) { NotificationIcon.SyncingReposCount++; - NotificationIcon.UpdateState (); + NotificationIcon.ShowState (); } if (args.Message.Equals ("FetchingFinished")) { NotificationIcon.SyncingReposCount--; - NotificationIcon.UpdateState (); + NotificationIcon.ShowState (); } } From 76d9a4e7076f79d9ef4fd1e5e73eb57a117e3f67 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Fri, 23 Jul 2010 00:35:54 +0100 Subject: [PATCH 34/37] sort authors alphabetically --- AUTHORS | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/AUTHORS b/AUTHORS index 3a579ae3..529e1fd3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -4,14 +4,16 @@ Maintainer: Contributors: - Lapo Calamandrei - Garrett LeSage Andreas Nilsson + Bertrand Lorentz + Garrett LeSage Jakub Steiner + Lapo Calamandrei Łukasz Jernaś - Sandy Armstrong - Philipp Gildein Oleg Khlystov + Philipp Gildein + Sandy Armstrong Simon Pither Steven Harms - Bertrand Lorentz + +Thanks very much! From 91625df1a362429ccc9c91a6b95de0b44c4da0a6 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Fri, 23 Jul 2010 00:50:40 +0100 Subject: [PATCH 35/37] Use a List instead of an Array for the repositories --- SparkleShare/SparkleStatusIcon.cs | 4 +-- SparkleShare/SparkleUI.cs | 42 +++++++++++++++---------------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/SparkleShare/SparkleStatusIcon.cs b/SparkleShare/SparkleStatusIcon.cs index e7849167..6e10534f 100644 --- a/SparkleShare/SparkleStatusIcon.cs +++ b/SparkleShare/SparkleStatusIcon.cs @@ -97,8 +97,7 @@ namespace SparkleShare { }; Menu.Add (FolderAction.CreateMenuItem ()); - Gtk.Action [] FolderItems = - new Gtk.Action [SparkleUI.Repositories.Length]; + Gtk.Action [] FolderItems = new Gtk.Action [SparkleUI.Repositories.Count]; int i = 0; foreach (SparkleRepo SparkleRepo in SparkleUI.Repositories) { @@ -202,7 +201,6 @@ namespace SparkleShare { } // Changes the status icon to the syncing animation - // TODO: There are UI freezes when switching back and forth // bewteen syncing and idle state public void SetSyncingState () { diff --git a/SparkleShare/SparkleUI.cs b/SparkleShare/SparkleUI.cs index 76f6bd72..6382ff4e 100644 --- a/SparkleShare/SparkleUI.cs +++ b/SparkleShare/SparkleUI.cs @@ -19,6 +19,7 @@ using Mono.Unix; using Mono.Unix.Native; using SparkleShare; using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -27,7 +28,7 @@ namespace SparkleShare { public class SparkleUI { private Process Process; - public static SparkleRepo [] Repositories; + public static List Repositories; // Short alias for the translations public static string _(string s) @@ -40,6 +41,8 @@ namespace SparkleShare { public SparkleUI (bool HideUI) { + Repositories = new List (); + Process = new Process (); Process.EnableRaisingEvents = true; Process.StartInfo.RedirectStandardOutput = true; @@ -65,7 +68,7 @@ namespace SparkleShare { NotificationIcon = new SparkleStatusIcon (); // Show a notification if there are no folders yet - if (Repositories.Length == 0) { + if (Repositories.Count == 0) { SparkleBubble NoFoldersBubble; NoFoldersBubble = new SparkleBubble (_("Welcome to SparkleShare!"), @@ -240,7 +243,9 @@ namespace SparkleShare { public void ShowNewCommitBubble (object o, SparkleEventArgs args) { - Console.WriteLine ("AAAAAAAAAAAAAAAAAA"); + + // Show bubble + } @@ -259,37 +264,32 @@ namespace SparkleShare { } - // TODO: Make this a List instead of an array + // Updates the list of repositories with all the + // folders in the SparkleShare folder public void UpdateRepositories () { - string SparklePath = SparklePaths.SparklePath; - // Get all the repos in ~/SparkleShare - SparkleRepo [] TmpRepos = new SparkleRepo [Directory.GetDirectories (SparklePath).Length]; - - int FolderCount = 0; - foreach (string folder in Directory.GetDirectories (SparklePath)) { + Repositories = new List (); + + foreach (string folder in Directory.GetDirectories (SparklePaths.SparklePath)) { // Check if the folder is a git repo if (Directory.Exists (SparkleHelpers.CombineMore (folder, ".git"))) { - TmpRepos [FolderCount] = new SparkleRepo (folder); - TmpRepos [FolderCount].NewCommit += ShowNewCommitBubble; - TmpRepos [FolderCount].FetchingStarted += UpdateStatusIcon; - TmpRepos [FolderCount].FetchingFinished += UpdateStatusIcon; - FolderCount++; + SparkleRepo repo = new SparkleRepo (folder); + + repo.NewCommit += ShowNewCommitBubble; + repo.FetchingStarted += UpdateStatusIcon; + repo.FetchingFinished += UpdateStatusIcon; + + Repositories.Add (repo); } } - - Repositories = new SparkleRepo [FolderCount]; - Array.Copy (TmpRepos, Repositories, FolderCount); - + } } -} - } From 32222bcfe6e8b12b387cb9bddeee182586de0888 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Fri, 23 Jul 2010 01:01:01 +0100 Subject: [PATCH 36/37] [eventlog] Don't allow selecting items --- SparkleShare/SparkleUI.cs | 13 ++++++++----- SparkleShare/SparkleWindow.cs | 4 ++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/SparkleShare/SparkleUI.cs b/SparkleShare/SparkleUI.cs index 6382ff4e..684e482b 100644 --- a/SparkleShare/SparkleUI.cs +++ b/SparkleShare/SparkleUI.cs @@ -135,15 +135,16 @@ namespace SparkleShare { } - string NotifySettingFile = SparkleHelpers.CombineMore (SparklePaths.SparkleConfigPath, + string notify_setting_file = SparkleHelpers.CombineMore (SparklePaths.SparkleConfigPath, "sparkleshare.notify"); // Enable notifications by default - if (!File.Exists (NotifySettingFile)) - File.Create (NotifySettingFile); + if (!File.Exists (notify_setting_file)) + File.Create (notify_setting_file); } - + + // Creates .desktop entry in autostart folder to // start SparkleShare automnatically at login public void EnableSystemAutostart () @@ -187,6 +188,8 @@ namespace SparkleShare { } + // Adds the SparkleShare folder to the user's + // list of bookmarked folders public void AddToBookmarks () { @@ -210,7 +213,7 @@ namespace SparkleShare { } - // Creates the 'SparkleShare' folder in the user's home folder if + // Creates the SparkleShare folder in the user's home folder if // it's not already there public void CreateSparkleShareFolder () { diff --git a/SparkleShare/SparkleWindow.cs b/SparkleShare/SparkleWindow.cs index 0c90cc3d..c8ac3c69 100644 --- a/SparkleShare/SparkleWindow.cs +++ b/SparkleShare/SparkleWindow.cs @@ -226,6 +226,10 @@ namespace SparkleShare { Spacing = 9 }; + icon_view.SelectionChanged += delegate { + icon_view.UnselectAll (); + }; + layout_vertical.PackStart (icon_view, false, false, 0); } From be597f237f9703df85376fb0506e48e41551abb7 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Fri, 23 Jul 2010 01:23:22 +0100 Subject: [PATCH 37/37] Only show first start screen when there are no folders --- SparkleShare/SparkleIntro.cs | 1 + SparkleShare/SparkleUI.cs | 28 ++++++++-------------------- 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/SparkleShare/SparkleIntro.cs b/SparkleShare/SparkleIntro.cs index f107d873..bcb3bd43 100644 --- a/SparkleShare/SparkleIntro.cs +++ b/SparkleShare/SparkleIntro.cs @@ -251,6 +251,7 @@ namespace SparkleShare { Button finish_button = new Button (_("Finish")); finish_button.Clicked += delegate (object o, EventArgs args) { + SparkleUI.NotificationIcon = new SparkleStatusIcon (); Destroy (); }; diff --git a/SparkleShare/SparkleUI.cs b/SparkleShare/SparkleUI.cs index 684e482b..10321154 100644 --- a/SparkleShare/SparkleUI.cs +++ b/SparkleShare/SparkleUI.cs @@ -59,32 +59,20 @@ namespace SparkleShare { UpdateRepositories (); + // Don't create the window and status // icon when --disable-gui was given if (!HideUI) { - SparkleIntro intro = new SparkleIntro (); - intro.ShowAll (); - - NotificationIcon = new SparkleStatusIcon (); - // Show a notification if there are no folders yet + // Show the intro screen if there are no folders if (Repositories.Count == 0) { - SparkleBubble NoFoldersBubble; - NoFoldersBubble = new SparkleBubble (_("Welcome to SparkleShare!"), - _("You don't have any folders set up yet.")); + SparkleIntro intro = new SparkleIntro (); + intro.ShowAll (); - NoFoldersBubble.IconName = "folder-sparkleshare"; - NoFoldersBubble.AddAction ("", _("Add a Folder…"), delegate { - SparkleDialog SparkleDialog = new SparkleDialog (""); - SparkleDialog.ShowAll (); -/* Process.StartInfo.FileName = "xdg-open"; - Process.StartInfo.Arguments = SparklePaths.SparklePath; - Process.Start (); -*/ - } ); - - NoFoldersBubble.Show (); + } else { + + NotificationIcon = new SparkleStatusIcon (); } @@ -247,7 +235,7 @@ namespace SparkleShare { public void ShowNewCommitBubble (object o, SparkleEventArgs args) { - // Show bubble + // TODO: Show bubble }