#!/usr/bin/perl
#
# FTPEdit
# Auth: Peter Edlund, peter.edlund@capgemini.se
#
# distributed under the GNU license,
# take your time and read it at www.gnu.org
#

#
# 0.1, 980529
# initial revision
#
# rev. 0.2, 980530
# added passive mode ftp
# added new file editing
# added of for configuration file
#
# rev. 0.3, 980602
# added no transmission if we didn't change anything
# added editor configuration
# added backup option
# added (a lot) more checks to see if everything is going alright
#
# rev. 0.4, 980605
# added default domain, to allow shorter command lines
#

#
# except this file, you will need to have Perl and the Net::FTP module
# installed and a file in your home directory named '.ftpedit',
# that should hold configuration values like these:
#
# If you want to have something else than the default editor (vi)
# you can, FIRST in the file, add a line like this
#
# editor    /your/preferred/editor
#
# then for each ftpsite you want to enable remote editing for add
# entries like this:
#
# [a name]                       - the 'domain'-name for the ftpserver
#                                  can be anything
# address   ftp.john.doe.com     - the ftp address
# user      johndoe              - your username
# password  your_password        - your password (optional)
#                                  if you define passwd, chmod 600 .ftpedit
# home      /your/remote/home    - the home directory on the ftp (optional)
#                                  I _really_ recommend setting the home
#                                  dir if you are doing much editing with
#                                  files in your home - since you wont have
#                                  to type the full path - 'ftpedit <domain>
#                                  .profile' will be enough to edit your
#                                  profile for instance
# savedir   /save/dir/locally    - if you want all edited files to be saved
#                                  locally as well (optional)
#                                  (a bonus with using savedir is that once
#                                   the file is saved locally you can use
#                                   'new' as an argument to skip the down-
#                                   load part, you already have the latest
#                                   file!)
# backup    yes                   - if set to 'yes', a 'file~' will be saved
#                                   on the ftpsite
#
# ALSO, the first 'domain' you specify in the configuration file will be
# set to 'default domain', meaning that you can access that domain without
# specifying any domain on the commandline (ex. 'ftpedit my/file.c' will do
# instead of 'ftpedit mydomain my/file.c')
#

#
# modules and pragmas
#
use strict;
use Net::FTP;

# predeclaration of subroutines
sub usage;
sub config_read;
sub basename;
sub query_passwd;
sub create_directories;
sub exiter;

# predeclaration of variables
my $temp  = "/tmp/ftpedit.$$";
my (@opts, $home, $file, $ftp, $first_mtime, $second_mtime);

#
# Initial variable checking
#

die "Couldn't find your home directory\n"
	unless $home = $ENV{'HOME'};
die "Couldn't find a configuration file\n"
	unless -f "$home/.ftpedit";

# read in the configuration file values
my %config = config_read("$home/.ftpedit");

#
# check the arguments and augment as needed
# specifically for primary domain
#
if(scalar(@ARGV) >= 1) {
	if(scalar(@ARGV) == 2 && $ARGV[1] eq 'new') {
		@opts = ( $config{'primary'}, $ARGV[0], $ARGV[1] );

	} elsif(scalar(@ARGV) == 1) {
		@opts = ( $config{'primary'}, $ARGV[0] );
	} else {
		@opts = @ARGV;
	}

} else { usage() }

my %params = %{ $config{$opts[0]} };

# check the basic configurational parameters
die "Domain $opts[0] not configured\n"
	unless %params;
die "Domain $opts[0] has no address configured\n"
	unless $params{'address'};
die "Domain $opts[0] has no username configured\n"
	unless $params{'user'};

# query for a password if its not defined in config file
$params{'password'} = query_passwd($params{'user'}, $opts[0])
	unless $params{'password'};


# what to do if the file we want doesn't start with '/'
if ($opts[1] !~ m!^/!) {
	if ($params{'home'}) {
		$opts[1] = $params{'home'} .'/'. $opts[1];
	} else {
		$opts[1] = '/'. $opts[1];
	}
}

#
# first, lets get the file from the remote system
# unless we're creating a new file that is
#

if ($params{'savedir'}) {
	$file = "$params{'savedir'}$opts[1]";
} else {
	$file = $temp;
}

# create eventual directories not already there in
# sub save structure
create_directories($file) if $params{'savedir'};

unless ($opts[2] eq 'new') {
	$ftp = Net::FTP->new($params{'address'}, Passive => 'true');

	unless ($ftp->login($params{'user'}, $params{'password'})) {
		print "Couldn't login to $params{'address'}!\n";
		exiter(1, $file, $temp);
	}
	unless ($ftp->get($opts[1], $file)) {
		print "Couldn't get $opts[1]. No read access?\n";
		exiter(1, $file, $temp);
	}

	$ftp->quit;
}

# take time for later checking if we really did anything to the file
if(-f $file) {
	$first_mtime = (stat($file))[9];
} else {
	$first_mtime = 1;
}


#
# now, let's do the actual editing
#
my $editor = $config{'editor'};
   $editor = 'vi' unless $editor;

system("$editor $file");



#
# and then we put it back into the system where it belongs
#
print "\n"; # extra linebreak for visuality

unless (-f $file) {
	print "Nothing edited, nothing will be transmitted.\n";

} else {
	$second_mtime = (stat($file))[9];

	unless ($second_mtime > $first_mtime) {
		print "No changes to file, nothing will be transmitted.\n";
	} else {

		$ftp = Net::FTP->new($params{'address'}, Passive => 'true');
		unless ($ftp->login($params{'user'}, $params{'password'})) {
			print "Couldnt login to $params{'address'}!\n";
      exiter(1, $file, $temp);
		}

    # if we should backup, lets rename the old file to file~
		# if rename isn't implemented this will just fail
		if ($params{'backup'} =~ /^y/i) {

			if ($ftp->rename($opts[1], sprintf("%s~", $opts[1]))) {
	     	printf "backed up %s%s\n", $params{'address'}, $opts[1];
			} else {
				printf "FAILED to back up %s%s\n", $params{'address'}, $opts[1];
			}
		}

		unless ($ftp->put($file, $opts[1])) {
			print "FAILED to write $file to remote location. No write access?\n";
			exiter(1, $file, $temp);
		}
		$ftp->quit;

		print "Saved in $params{'address'}$opts[1]\n";
	}
}

# exit successfully
exiter(0, $file, $temp);

################################################################################
# SUBROUTINES
#

sub exiter {
	my ($value, $file, $temp) = @_;

	qx/rm -f $temp/ if $file eq $temp;
	exit $value;
}
	
sub usage {
	printf "
Usage:
%s <domain> <file> <new>

domain: the domainname specified in the configuration file
        if you want to connect to first domain specified in the
        configuration file, you need not pass this argument
file:   the file to edit
        if <file> doesn't start with '/', your home directory
        will be prepended (if it is specified in ~/.ftpedit
new:    if you add 'new' at the end, you will create a new file
        to be stored (ie, I wont try to get the file, only store it)

", basename($0);
	exit 0;
}

sub create_directories {
	my ($file) = shift;
	my $root = "";

	my @arr = split(/\//, $file);
	foreach (@arr) {
		next if $_ eq $arr[$#arr];
		unless (-d qq($root/$_)) {
			die "Couldn't create directiry $root/$_\n"
				unless mkdir(qq($root/$_), 0755);
		}
		$root .= "/$_";
	}
}

sub query_passwd {
	my ($user, $domain) = @_;

	print "Please give a password for user $user on domain $domain:\n";
	print "Password: ";

	my $answer = <STDIN>;
	chop($answer);
	return $answer;
}

sub basename {
	my ($foo) = shift;

	$foo =~ s/.*\/(.*)/$1/;
	return $foo;
}

sub config_read {
	my ($file, $flag) = @_;
	my (%hash, $main, $key, $value, $primary_dom);

	return 0 unless -f $file;

	open (FH, $file) || return 0;
	while (<FH>) {

		next if /^[\s\t]*#/ || /^\W*$/; # skip comments and blanks
		s/(^.*?)\W*#.*/$1/;             # remove end-of-line comments

		unless ($flag) {
			$main = $1, next if /^\W*\[(.+)\]/;
			$primary_dom = $main unless $primary_dom; # set pri domain
		}
		next unless ($key, $value) = split (/[\s\t]+/, $_, 2);
		chomp ($value);

		if ($main) {
			$hash{$main}{$key} = $value;
		} else {
			$hash{$key} = $value;
		}
	}
  $hash{'primary'} = $primary_dom;
	return %hash;
}