Sign in or Sign up

[Ruby] Basic FTP Client
Started by Dox


Rate this topic
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5


0 posts in this topic
Dox Offline
Cyber Security Student
***


Contributor
Posts: 414
Threads: 125
Joined: Mon Oct 2017
Reputation: 6

CZPoints: 136 CZP
OnFireContributor
10-12-2017, 06:16 PM -
#1
Code:
#!/usr/bin/ruby

require 'tk'
require 'net/ftp'

# Close the connection and terminate pgm.
def term(conn)
 if conn
   begin
     conn.quit
   ensure
     conn.close
      
   end
 end
 exit
end

# Display an error dialog.
def thud(title, message)
 Tk.messageBox('icon' => 'error', 'type' => 'ok',
               'title' => title, 'message' => message)
end

# This is the login window.  It pops up and asks for the remote host and the
# user credentials, and a button to initiate the login when the fields are
# ready.
class LoginWindow
 # Generate s label/entry pair for the login window.  These will be
 # appropriately gridded on row row inside par.  Text box has width
 # width and places its contents into the reference $ref.  If $ispwd,
 # treat it as a password entry box.  Returns the text variable which
 # gives access to the entry.
 def genpair(row, text, width, ispwd=false)
   tbut = TkLabel.new(@main, 'text' => text) {
     grid('row' => row, 'column' => 0, 'sticky' => 'nse')
   }
   tvar = TkVariable.new('')
   lab = TkEntry.new(@main) {
     background 'white'
     foreground 'black'
     textvariable tvar
     width width
     grid('row' => row, 'column' => 1, 'sticky' => 'nsw')
   }
   lab.configure('show' => '*') if ispwd

   return tvar
 end

 # Log into the remote host.  If successful, start the directory loader.
 # Modes are: 1: Anonymous, 2: User, 3: Return, which does anon if the
 # user infor was not filled in, and user otw.
 def do_login(mode)
   host = @host.value
   acct = @acct.value
   password = @password.value

   # Adjust user data by mode.
   if mode == 1 || (mode == 3 && acct == "" && password == "")
     acct ='anonymous'
     if password == ""
       password = 'anonymous'
     end
   end

   # Make sure we're all filled in.
   if host == "" || acct == "" || password == ""
     thud('No Login Info',
          "You must provide a hostname and login credentials.")
     return
   end

   # Attempt to connect to the remote host and log in
   begin
     @conn = Net::FTP.new(host, acct, password)
     @conn.passive = true
   rescue
     thud("Login Failed", $!)
     @conn = nil
     return
   end

   # Display the listing in the window.
   @listwin.setconn(@conn)
   @main.destroy()
 end

 def initialize(main, listwin, titfont, titcolor)
   @main = TkToplevel.new(main)
   @main.title('FTP Login')

   # Listing window.
   @listwin = listwin
   @conn = nil

   # This counts through the rows, which makes it easier to modify
   # the program.
   row = -1

   # Label at the top of window.
   toplab = TkLabel.new(@main) {
     text "FTP Server Login"
     justify 'center'
     font titfont
     foreground titcolor
     grid('row' => (row += 1), 'column' => 0, 'columnspan' => 2,
          'sticky' => 'news')
   }

   # Hostname entry
   @host = genpair(row += 1, 'Host:', 25)

   # Login buttons, in a frame for layout.
   bframe = TkFrame.new(@main) {
     grid('row' => (row += 1), 'column' => 0, 'columnspan' => 2,
          'sticky' => 'news')
   }
   TkButton.new(bframe, 'command' => proc { self.do_login(1) }) {
     text 'Anon. Login'
     pack('side' => 'left', 'expand' => 'yes', 'fill' => 'both')
   }
   TkButton.new(bframe, 'command' => proc { self.do_login(2) }) {
     text 'User Login'
     pack('side' => 'left', 'expand' => 'yes', 'fill' => 'both')
   }

   # Login and password entries.
   @acct = genpair(row += 1, 'Login:', 15)
   @password = genpair(row += 1, 'Password:', 15, true)

   stop = TkButton.new(@main, 'command' => proc { term(@conn) } ) {
     text 'Exit'
     grid('row' => (row += 1), 'column' => 0, 'columnspan' => 2,
          'sticky' => 'news')
   }

   # CR same as pushing login.
   @main.bind('Return', proc { self.do_login(3) })
 end
end

# This is a window containing the file listing.
class FileWindow < TkFrame
 def initialize(main)
   super

   # Set up the title appearance.
   titfont = 'arial 16 bold'
   titcolor = '#228800'

   @conn = nil

   # Label at top.
   TkLabel.new(self) {
     text 'FTP Download Agent'
     justify 'center'
     font titfont
     foreground titcolor
     pack('side' => 'top', 'fill' => 'x')
   }

   # Status label.
   @statuslab = TkLabel.new(self) {
     text 'Not Logged In'
     justify 'center'
     pack('side' => 'top', 'fill' => 'x')
   }

   # Exit button
   TkButton.new(self) {
     text 'Exit'
     command { term(@conn) }
     pack('side'=> 'bottom', 'fill' => 'x')
   }

   # List area with scroll bar.  The list area is disabled since we
   # don't want the user to type into it.
   @listarea = TkText.new(self) {
     height 10
     width 40
     cursor 'sb_left_arrow'
     state 'disabled'
     pack('side' => 'left')
     yscrollbar(TkScrollbar.new).pack('side' => 'right', 'fill' => 'y')
   }

   # Bind the system exit button to our exit.
   main.protocol('WM_DELETE_WINDOW', proc { term(@conn) } )

   # Create the login window.
   LoginWindow.new(main, self, titfont, titcolor)
 end

 # Change the color of a tag for entering and leaving.  Unfortunately, there
 # is no active color for tags in a text box.
 def recolor(tag, color)
   @listarea.tag_configure(tag, 'foreground' => color)
 end

 # Do a CD and load the contents.  If there is no directory name, skip
 # the CD.
 def load_dir(dir)
   if dir
     begin
       @conn.chdir(dir)
     rescue
       thud('No ' + dir, $!)
     end
     @statuslab.configure('text' => "[Loading " + dir + "]")
   else
     @statuslab.configure('text' => '[Loading Home Dir]')
   end
   update

   # Get the list of files.
   files = [ ]
   dirs = [ ]
   sawdots = false
   @conn.list() do |line|
     # Real lines start with the perm bits.  And we don't want specials.
     if line =~ /^[\-d]([r\-][w\-][x\-]){3}/
       # Extract the useful parts, toss the bones.  The limit keeps us from
       # dividing file names containing spaces.
       parts = line.split(/\s+/, 9)
       if parts.length >= 9
         fn = parts.pop()
         sawdots = true if fn == '..'
         if parts[0][0..0] == 'd'
           dirs.push(fn)
         else
           files.push(fn)
         end
       end
     end
   end

   # Add .. if not present, then sort the list.
   dirs.push('..') unless sawdots
   files.sort!
   dirs.sort!

   # Clear the old contents from the directory listing box.
   @listarea.configure('state' => 'normal')
   @listarea.delete('1.0', 'end')

   # Fill in the directories.  Bind for directory load (us).
   ct = 0
   while fn = dirs.shift
     tagname = "fn" + ct.to_s
     @listarea.insert('end', fn+"\n", tagname)
     @listarea.tag_configure(tagname, 'foreground' => '#4444FF')
     @listarea.tag_bind(tagname, 'Button-1',
                        proc { |f| self.load_dir(f) }, fn)
     @listarea.tag_bind(tagname, 'Enter',
                        proc { |t| self.recolor(t, '#0000aa') },
                                   tagname)
     @listarea.tag_bind(tagname, 'Leave',
                        proc { |t| self.recolor(t, '#4444ff') },
                                   tagname)
     ct += 1
   end

   # Fill in the files. Bind for download.
   while fn = files.shift
     tagname = "fn" + ct.to_s
     @listarea.insert('end', fn+"\n", tagname)
     @listarea.tag_configure(tagname, 'foreground' => 'red')
     @listarea.tag_bind(tagname, 'Button-1',
                        proc { |f| self.dld_file(f) }, fn)
     @listarea.tag_bind(tagname, 'Enter',
                        proc { |t| self.recolor(t, '#880000') },
                                   tagname)
     @listarea.tag_bind(tagname, 'Leave',
                        proc { |t| self.recolor(t, 'red') },
                                   tagname)
     ct += 1
   end

   # Lock it up so the user can't mess with it.
   @listarea.configure('state' => 'disabled')

   # Update the status label.
   begin
     loc = @conn.pwd()
   rescue
     thud('PWD Failed', $!)
     loc = '???'
   end
   @statuslab.configure('text' => loc)
 end

 # Download the file.
 def dld_file(fn)
   # Announce.
   @statuslab.configure('text' => "[Retrieving " + fn + "]")
   update

   # Get the file.
   begin
     @conn.getbinaryfile(fn)
   rescue
     thud('DLD Failed', fn + ': ' + $!)
     @statuslab.configure('text' => '')
   else
     @statuslab.configure('text' => 'Got ' + fn)
   end
 end

 # This is a hook that the login window calls after a successful login.
 # The login window makes the connection and attempts to login.  When this
 # succeeds, it calls setconn() and destroys itself.  Setconn records the
 # connection (which the login box created), then does the initial
 # directory load.
 def setconn(conn)
   @conn = conn
   load_dir(nil)
 end
end

# Create the main window, set the default colors, create the GUI, then
# fire the sucker up.
BG = '#E6E6FA'
root = TkRoot.new('background' => BG) { title "FTP Download" }
TkOption.add("*background", BG)
TkOption.add("*activebackground", '#FFE6FA')
TkOption.add("*foreground", '#0000FF')
TkOption.add("*activeforeground", '#0000FF')
FileWindow.new(root).pack()

Tk.mainloop
(This is not for practical use! Just to test with!)
Contact E-Mail: [email protected]
BTC Address: 1JmJrbmQn4Bg24r68bSyZ7TxNZGad3iPWM




Users browsing this thread: 1 Guest(s)