#!/usr/local/bin/perl -w # Author: Eric Marsden # Version: 0.1 # Copyright: (C) 2000 Eric Marsden # Time-stamp: <2000-08-25 emarsden> # # 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 2 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, write to the Free # Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, # MA 02111-1307, USA. # # The latest version of this package should be available from # # # A commandline Squid-enabled ftp client. # # Squid is a popular free caching # HTTP proxy which is able to handle FTP requests. Certain # organizations have firewall configurations which block outbound FTP # connections, but do allow connections via Squid. While some programs # such as Netscape are able to use this type of proxy, other programs # which depend on the standard commandline ftp client (such as Emacs' # support for transparent ftp file access) don't work. # # This program is an attempt to work around this problem. It tries to # look like the standard Unix ftp client, but instead of opening TCP # connections to the remote ftp servers, it makes HTTP requests to # your Squid proxy. It provides only a small subset of the commands # normally understood by a standard ftp client (indeed, only those # which are used by Emacs' ange-ftp module). # # Consider the data transformations which occur when retrieving a # remote directory listing using this program and be amazed if it # happens to work: # # 1. remote ftpd uses readdir() and stat() to generate an ASCII # directory listing # 2. Squid retrieves this listing and tranforms it into HTML # 3. this program reconstructs the ASCII listing from the HTML # 4. ange-ftp parses the ASCII listing # # # -=- EMACS CONFIGURATION -=- # # To configure Emacs to use this program instead of your regular ftp # client, add something like the following to your ~/.emacs # initialization file: # # (setq ange-ftp-ftp-program-name "/path/to/squidftp") # (setq ange-ftp-program-args nil) # (setq ange-ftp-send-hash nil) # # If you are using XEmacs the corresponding incantations should be # # (setq efs-ftp-program-name "/path/to/squidftp") # (setq efs-ftp-program-args nil) # (setq efs-send-hash nil) # # # The program will determine the address of your FTP proxy by looking # at the value of the environment variable `ftp_proxy'. Set this in # your shell's initialization file to something like # # export ftp_proxy=http://webcache:3128/ # # # If ange-ftp seems to get hung while retrieving a directory listing, # or retrieves the same directory many times, try adding the following # lines to your Emacs initialization file: # # (defadvice ange-ftp-raw-send-cmd (before ecm-ftp-pause activate) # (sleep-for 0 100)) # # # # -=- BUG REPORTS -=- # # When reporting bugs related to this program's interaction with # Emacs, please include in your email the contents of the *ftp ...* # buffer for a failed session. # # # -=- TODO -=- # # * rewrite in a real programming language # * implement missing commands package SquidFTP; use Term::ReadLine; use LWP::UserAgent; use Text::ParseWords (); use IO::File; use IO::Handle; use strict "vars"; use strict "refs"; use vars qw($ua $term %status); sub main { my($prompt, $line); $ua = new LWP::UserAgent; $ua->env_proxy(); $ua->agent("SquidFTP/0.1 "); $status{'pwd'} = '/'; $status{'user'} = 'anonymous'; $status{'passwd'} = 'anonftp@'; $status{'type'} = 'a'; $prompt = "ftp> "; $term = new Term::ReadLine 'SquidFTP client'; while (1) { $line = $term->readline($prompt) or bye(); &handleLine($line); $term->addhistory($line) if $line =~ /\S/; } } sub handleLine { my($line) = @_; my(@word, $command); @word = Text::ParseWords::shellwords($line); $command = shift @word; eval { SquidFTP->$command(@word) } if defined $command; warn "?Invalid command " . $@ if $@; } sub wash { my($data) = @_; my($washed, $line, $total, $re, $file); $re = " " . "\n
"));
    $data = substr($data, 0, rindex($data, "
")); $total = 0; while ($data =~ m|$re|g) { if ($2 eq "DIR") { $line = sprintf("drwxr-xr-x 1 root root %10d ", 512); $file = $1; } elsif ($2 eq "LINK") { $line = sprintf("drwxr-xr-x 1 root root %10d ", 512); $file = $1 . "/"; } else { $line = sprintf("-r--r--r-- 1 root root %10d ", defined $6 ? $6 * 1024: 1024); $file = $1; } $line .= sprintf("%3s %2d %5s %s", $3, $4, $5, $file); $washed .= $line . "\n"; $total++; } $washed = "total $total\n$washed"; } else { $washed = $data; } return $washed; } sub open { my($self) = shift; my(@arg) = @_; $status{'host'} = $arg[0]; print "Connected to " . $arg[0] . "\n"; print "220 " . $arg[0] . " FTP server (Squid-proxied) ready.\n"; } sub user { my($self) = shift; my(@arg) = @_; $arg[0] =~ s/^"?(.*)"?$/$1/; $status{'user'} = $arg[0]; print "331 Password please.\n"; if (defined $arg[1]) { $arg[1] =~ s/^"?(.*)"?$/$1/; $status{'passwd'} = $arg[1]; } else { $status{'passwd'} = $term->readline("Password: "); } print "230 OK buddy.\n"; } sub ls { my($self) = shift; my(@arg) = @_; my($from, $to, $sink, $url, $request, $response); die "Not connected" unless defined $status{'host'}; if (defined $arg[0]) { $from = $arg[0]; if ($from =~ m|^-alF (.*)$|) { $from = $1; } } else { $from = $status{'pwd'}; } if (defined $arg[1]) { $to = $arg[1]; $sink = new IO::File("> $to") or die "Cannot open $to"; } else { $sink = new IO::Handle; $sink->fdopen(STDOUT, "w") or die "Cannot open stdout"; } $url = new URI; $url->scheme("ftp"); $url->userinfo($status{'user'} . ":" . $status{'passwd'}); $url->host($status{'host'}); $url->path("$from;type=" . $status{'type'}); $request = new HTTP::Request('GET', $url->as_string()); $request->header(Accept => "text/html, */*;q=0.1"); $response = $ua->request($request); print "200 PORT command successful.\n"; print "150 Opening data connection for $from\n"; if ($response->is_success) { print $sink wash($response->content); } else { print "500 Error " . $response->status_line . "\n"; } close($sink); print "226 Transfer complete.\n"; } sub dir { my($self) = shift; my(@arg) = @_; my($from, $to, $sink, $url, $request, $response); die "Not connected" unless defined $status{'host'}; if (defined $arg[0]) { $from = $arg[0]; } else { $from = $status{'pwd'}; } if (defined $arg[1]) { $to = $arg[1]; $sink = new IO::File("> $to") or die "Cannot open $to"; } else { $sink = new IO::Handle; $sink->fdopen(STDOUT, "w") or die "Cannot open stdout"; } $url = new URI; $url->scheme("ftp"); $url->userinfo($status{'user'} . ":" . $status{'passwd'}); $url->host($status{'host'}); $url->path("$from;type=" . $status{'type'}); $request = new HTTP::Request('GET', $url->as_string()); $request->header(Accept => "text/html, */*;q=0.1"); $response = $ua->request($request); print "200 PORT command successful.\n"; print "150 Opening data connection for $from\n"; if ($response->is_success) { print $sink wash($response->content); } else { print "500 Error " . $response->status_line . "\n"; } close($sink); print "226 Transfer complete.\n"; } sub get { my($self) = shift; my(@arg) = @_; my($from, $to, $sink, $url, $request, $response); die "Not connected" unless defined $status{'host'}; if (defined $arg[0]) { $from = $arg[0]; } else { $from = $status{'pwd'}; } if (defined $arg[1]) { $to = $arg[1]; $sink = new IO::File("> $to") or die "Cannot open $to"; } else { $sink = new IO::Handle; $sink->fdopen(STDOUT, "w") or die "Cannot open stdout"; } $url = new URI; $url->scheme("ftp"); $url->userinfo($status{'user'} . ":" . $status{'passwd'}); $url->host($status{'host'}); $url->path("$from;type=" . $status{'type'}); $request = new HTTP::Request('GET', $url->as_string()); $request->header(Accept => "text/html, */*;q=0.1"); $response = $ua->request($request); print "200 PORT command successful.\n"; print "150 Opening data connection for $from\n"; if ($response->is_success) { print $sink wash($response->content); } else { print "500 Error " . $response->status_line . "\n"; } print "226 Transfer complete.\n"; } sub hash { print "Hash mark printing on (1024 bytes/hash mark).\n"; } sub pwd { my($self) = shift; my(@arg) = @_; print "257 \"" . $status{'pwd'} . "\" is current directory.\n"; } sub cd { my($self) = shift; my(@arg) = @_; if (! defined $arg[0]) { $status{'pwd'} = "/"; } elsif (substr($arg[0], 0, 1) eq "/") { $status{'pwd'} = $arg[0]; } elsif ($status{'pwd'} eq "/") { $status{'pwd'} .= $arg[0]; } else { $status{'pwd'} .= "/" . $arg[0]; } print "250 CWD command successful.\n"; } sub lcd { my($self) = shift; my(@arg) = @_; chdir $arg[0]; print "Local directory now " . $arg[0] . "\n"; } sub type { my($self) = shift; my(@arg) = @_; if ($arg[0] eq "ascii") { $status{'type'} = 'a'; print "200 Type set to A.\n"; } elsif ($arg[0] eq 'binary') { $status{'type'} = 'i'; print "200 Type set to I.\n"; } else { print $arg[0] . ": unknown mode\n"; } } sub close { $status{'host'} = undef; $status{'type'} = 'a'; $status{'pwd'} = "/"; print "221 Goodbye.\n"; } sub bye { print "221 Goodbye.\n"; exit(0); } &main(); 1; # EOF