Class | SSHMenu::SetupWizard |
In: |
lib/sshmenu.rb
(Git)
|
Parent: | Object |
The SSHMenu::SetupWizard class is used only once - when the menu program is first run and the config file does not yet exist. The role of the ‘wizard’ is to pull hostnames out of the user‘s .ssh/known_hosts file and add them to the menu. The user may chose to skip this process and manually add the required hosts.
Sadly this process turns out to be not very useful on systems with the HashKnownHosts option is enabled.
This is the main entry point. The SSHMenu::Config class will construct a SetupWizard object and then call this method on it. This method will call run_wizard to do the work and then return a list of SSHMenu::HostItem objects (possibly an empty list).
# File lib/sshmenu.rb, line 2741 def autoconfigure(config) @imported = {} @hashed_hosts_skipped = 0 @known_hosts_file = config.home_dir + '.ssh/known_hosts' return unless @known_hosts_file.readable? return unless run_wizard host_list = [] @imported.keys.sort.each do |name| host_list << { 'type' => 'host', 'title' => name, 'sshparams' => "-AX #{@imported[name]}" } end return host_list end
Given a list of addresses, returns the FQDN of the first one to resolve or if none resolve, returns the last name from the list (on the assumption it was added most recently).
# File lib/sshmenu.rb, line 3031 def best_address(*aliases) # Return the first one that successfully resolves aliases.each do |a| begin return a if canonical_name(a) rescue # Ignore it and try the next one end end return aliases[-1] end
Given a list of aliases for the same host, returns the ‘best’ one. The shortest alias which is not an IP address is preferred. Otherwise, IP addresses are looked up via DNS and the fully qualified domain name of the first one to successfully resolve is returned.
# File lib/sshmenu.rb, line 3007 def best_alias(aliases) numeric_addr = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ # Return shortest host or domain name if there is one names = aliases.find_all { |h| !h.match(numeric_addr) } return names.sort { |a,b| a.length <=> b.length }[0] if names.length > 0 # Otherwise, try and resolve an address to a name aliases.each do |a| begin return canonical_name(a) rescue # Ignore it and try the next one end end return aliases[0] end
Used to contruct the dialog box which will have its body contents replaced at each step.
# File lib/sshmenu.rb, line 2907 def build_dialog dialog = Gtk::Dialog.new( "Initial Setup", nil, Gtk::Dialog::MODAL | Gtk::Dialog::NO_SEPARATOR, [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_REJECT] ) dialog.default_response = Gtk::Dialog::RESPONSE_ACCEPT table = Gtk::Table.new(2, 2, false) table.border_width = 0 bgcolour = dialog.style.bg(Gtk::STATE_SELECTED) table.attach( Gtk::EventBox.new.modify_bg(Gtk::STATE_NORMAL, bgcolour).add(wizard_icon), 0, 1, 0, 1, Gtk::FILL, Gtk::FILL, 0, 0 ) title = '<span size="xx-large" weight="heavy" foreground="white">' + ' SSH Menu: Initial Setup</span>' table.attach( Gtk::EventBox.new.modify_bg(Gtk::STATE_NORMAL, bgcolour).add( Gtk::Label.new(title).set_use_markup(true).set_width_chars(48).set_xalign(0) ), 1, 2, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::FILL, 0, 0 ) table.attach( Gtk::EventBox.new.modify_bg(Gtk::STATE_NORMAL, bgcolour).add( Gtk::Label.new("\n\n\n\n\n\n\n\n\n\n") ), 0, 1, 1, 2, Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 0, 0 ) @body = Gtk::Frame.new.set_shadow_type(Gtk::SHADOW_NONE) table.attach(@body, 1, 2, 1, 2, Gtk::FILL, 0, 20, 20) dialog.vbox.add(table) dialog.show_all return dialog end
Given an address, returns the fully qualified domain name. May raise a ‘host not found’ exception
# File lib/sshmenu.rb, line 3048 def canonical_name(addr) inet = Socket.gethostbyname(addr) info = Socket.gethostbyaddr(inet[3]) return info[0] end
This routine does the work of processing the known hosts file and updating the progress bar. It is an ‘idle handler’ so it is called multiple times until it returns false to indicate no further work remains. Calls list_host_aliases on the first invocation to get a list of raw host aliases. Each subsequent invocation processes one raw host entry to resolve names and addresses (for duplicate filtering).
# File lib/sshmenu.rb, line 2882 def import_tick(dialog, progress) if @raw_hosts.nil? @raw_hosts = list_host_aliases @curr_host = 0 progress.fraction = (@curr_host + 1.0) / (@raw_hosts.length + 1.0) return true end if @curr_host >= @raw_hosts.length setup_step_three(@raw_hosts.length) return false # no more work to do end aliases = @raw_hosts[@curr_host] @curr_host += 1 name = best_alias(aliases) addr = best_address(name, *aliases) @imported[name] = addr progress.fraction = (@curr_host + 1.0) / (@raw_hosts.length + 1.0) return true end
Reads the known hosts file, skipping hashed host entries and collecting hostnames from the remaining entries - returns a list of hostnames.
# File lib/sshmenu.rb, line 2987 def list_host_aliases hosts = {} @known_hosts_file.each_line do |line| if line =~ /^[|]\d+[|]/ @hashed_hosts_skipped += 1 elsif line =~ /^([^|]\S*)\s+ssh-\S+\s+(\S+)/ aliases, key = $1, $2 hosts[key] ||= [] aliases.split(/,/).each { |h| hosts[key] << h } end end return hosts.values end
Creates a dialog by calling build_dialog. Populates the body of the dialog by calling setup_step_one and awaits user interaction. Replaces the body of the dialog by calling setup_step_two and repeats. Returns true if the process was completed or false if the user pressed cancel.
# File lib/sshmenu.rb, line 2763 def run_wizard @dialog = build_dialog setup_step_one if @dialog.run != Gtk::Dialog::RESPONSE_ACCEPT @dialog.destroy return false end setup_step_two if @dialog.run != Gtk::Dialog::RESPONSE_ACCEPT @dialog.destroy return false end @dialog.destroy return true end
Replaces the body of the wizard dialog box with the supplied widget.
# File lib/sshmenu.rb, line 2857 def set_body(widget) @body.each { |w| @body.remove(w) } @body.add(widget) @body.show_all end
Replaces the dialog‘s button box contents with the supplied list of buttons. Each button is represented as a two element array. The first element is the button text label and the second is the response code the button should generate.
# File lib/sshmenu.rb, line 2868 def set_buttons(*buttons) bbox = @dialog.action_area bbox.each { |w| bbox.remove(w) } buttons.each { |b| @dialog.add_button(*b) } @dialog.default_response = buttons.last[1] end
Sets up an introductory message describing the function of the wizard and provides Next and Cancel buttons (cleverly disguised as ‘Automatic Setup’ and ‘Manual Setup’ buttons respectively).
# File lib/sshmenu.rb, line 2785 def setup_step_one set_body( Gtk::Label.new( "The initial menu options can be created automatically\n" + "from your list of SSH known hosts.\n\n" + "Or, if you prefer, you can set up the menu manually." ).set_xalign(0) ) set_buttons( ["Manual Setup", Gtk::Dialog::RESPONSE_REJECT], ["Automatic Setup", Gtk::Dialog::RESPONSE_ACCEPT] ) end
Called from import_tick when all the known_hosts entries have been processed.
# File lib/sshmenu.rb, line 2832 def setup_step_three(count) vbox = Gtk::VBox.new vbox.spacing = 10 text = "#{count} host#{count == 1 ? '' : 's'} imported" if @hashed_hosts_skipped > 0 text = text + " (#{@hashed_hosts_skipped} hashed hostnames were skipped)" end vbox.pack_start( Gtk::Label.new(text).set_xalign(0) ) progress = Gtk::ProgressBar.new progress.fraction = 1.0 vbox.pack_start(progress) set_body(vbox) set_buttons( ["Cancel", Gtk::Dialog::RESPONSE_REJECT], ["Finish", Gtk::Dialog::RESPONSE_ACCEPT] ) end
Sets up a progress bar and registers import_tick as an ‘idle’ handler to do the work. The user can ‘Cancel’ at any time and can click ‘Finish’ once import_tick has processed the whole known_hosts file.
# File lib/sshmenu.rb, line 2805 def setup_step_two vbox = Gtk::VBox.new vbox.spacing = 10 vbox.pack_start( Gtk::Label.new( "Importing known hosts. Please wait ...").set_xalign(0) ) progress = Gtk::ProgressBar.new vbox.pack_start(progress) set_body(vbox) set_buttons( ["Cancel", Gtk::Dialog::RESPONSE_REJECT], ["Finish", Gtk::Dialog::RESPONSE_ACCEPT] ) @dialog.set_response_sensitive(Gtk::Dialog::RESPONSE_ACCEPT, false) @raw_hosts = nil Gtk.idle_add { import_tick(@dialog, progress) } end
Returns a Gtk::Image being the SSHMenu logo.
# File lib/sshmenu.rb, line 2955 def wizard_icon loader = Gdk::PixbufLoader.new loader.write(Base64.decode64("iVBORw0KGgoAAAANSUhEUgAAAEQAAABACAMAAACUXCGWAAAAw1BMVEXIAAAcHhslKCUrLSoyNTI3\nODA4Ozg7OzQ/QD47Qz9DQTpDREJISkdMSj1LSkNRUEhQUk9VUkVRWVVWWFVbWExZWVFeYF1aYl5h\nYFhjYFNhaWVqZ1pmaGVpaGBwb2dzb2JucG17dmN3dm51d3R7d2p8fnuAf3eHg3aJhHGGh4SJiICN\nj4yRkIeWk4WYmI+dn5yloZOjoZmuqpytq6OqrKmzsam6ubHEw7vOzcXY187W2NXe3dTd39zo6ebv\n8e79//sAAQCpFywPAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAAC4cAAAuHAZNA\nh1MAAAAHdElNRQfVDAsBLCiRkORfAAAC1klEQVRYw+3WbVOiUBTA8ZV7QwrUuxCIBSyB0opIGYLG\natzv/6n2HASz2uH6olc7/mf02kz8PDwM8uPHpUuXviF+Zp3Gfv/2sX3TrnzdbrebzaYoijxLuhT+\n9or9OQn/3rbb53mWrVarNBIjn4ja2B5myGsjDUQI/vOxHMuyw/enSRLHUZSegRSig5qliRDJua5b\n4/Gd49zd1W9jzNB1xjRVkSnPk8QTI9r80AwKw9D3fQeyIMMwCM+SuBvZbwEZz1qiMVoEDEZ5FguR\nbcbZrC38Mggj/CWOXDFihe/EJwMneY7OQfRmY7/dk4NhGQwiiNjdyGaz4o4fWnAmVM2wLKYqiqIZ\nTFGYocmyxq4ACc5AdN9Xn3ZVVS4UpVlptVOYXJUyThIEZieyK+pJ/HW19sOiWrerVJVUI1VJ4Jgs\nA68bKYsi5brjlBWVCFmEzToDpEd6iEh86YmQHBC4Tn3YifWCSc1KpKquJBpM4rojAZInOAml1ny9\nr+ZSs/aqnazKiJAzkAxuOLblzJ8Iha8vYSUS7gtsr0jNJLbdjWyzLOa6ZT1VBR7dJ1wdWBFR8cBq\nEn+07WE3slrFfGxZ8qI+tZQ2K8GzQ+s3QEwBkq4izvAakymVVVavVNFUSlVNgU8qTGIKkE26jPjY\nOIkd0jAVoojcdCJFArc+zfjYBwUmGY26EbhrBdz8ShwNhfCpEIkfPX7N2Md9ORC1oUiADPudSIYI\n+9yJgchQhESPLleuIfVaVeCFH2U8QwSvOknq9fjDcNCNrKZTW/ST8TAQIOl0unw59nxoWff4u0mM\n/DrtHppMJubt7e1PbNDWjSRfiUlLIGJ67kiM3J80aYh3YxAMBjdCJH4n3H8YsHkf37svNnc4OjbE\nbtr62FVTnwset0IvCKJoHs8XWP2DGkWzMPA8z3GxG8HjFjLWEKYxTdt1fNgONrJt2zRxsGYmwr/l\n0e/ygHzp0v/WX7AH3cFL3lx/AAAAAElFTkSuQmCC\n" )) loader.close return Gtk::Image.new(loader.pixbuf) end