#!/usr/bin/perl

# take a look at IPC::Run, suggested by @woggle in #perlhelp on efnet

use strict;
use Getopt::Std;
use Data::Dumper;

my $crontab = "/usr/bin/crontab";

my %options;
my $ret = getopts('u:ler', \%options);

unless ($ret) {
	exec($crontab);
}

my $optionssum = $options{l} + $options{e} + $options{r};
if ($optionssum > 1) {
	exec ($crontab, ($options{e} ? '-e' : ()), ($options{r} ? '-r' : ()), ($options{l} ? '-l' : ()));
} elsif ($optionssum == 0 && !@ARGV) {
	exec ($crontab);
}

if ($options{l}) { # list the crontab
	my @exec = ($crontab, ($options{u} ? ('-u', $options{u}) : () ), '-l', @ARGV);
	exec(@exec);
} elsif ($options{e}) { # edit the crontab
	# check the existing crontab for mailto lines
	my @lines = safe_exec_with_output($crontab, ($options{u} ? ('-u', $options{u}) : ()), '-l');
	unless (&checkForMailto(@lines)) {
		unless (-t STDIN) {
			print "Fatal error: stdin is not a terminal!\n";
			exit 1;
		}
		my $mailto = &getMailto;
		unshift @lines, ("MAILTO=\"$mailto\"");
		&newCrontab($options{u}, @lines);
	}

	exec($crontab, ($options{u} ? ('-u', $options{u}) : ()), '-e');
} elsif ($options{r}) { # remove their existing crontab.  we'll just send that straight to the crontab command
	exec($crontab, ($options{u} ? ('-u', $options{u}) : ()), '-r', @ARGV);
} else { # they're providing us with a crontab file!  woo!
	if (scalar(@ARGV) > 1) {
		print STDERR "only one file argument is allowed\n";
		exit 1;
	}

	my $file;
	if ($ARGV[0] eq '-') {
		$file = \*STDIN;
	} else {
		unless (-e $ARGV[0]) {
			print "problem opening file ($ARGV[0]): no such file or directory\n";
			exit 1;
		}
		unless (-f $ARGV[0]) {
			print "problem opening file ($ARGV[0]): not a regular file\n";
			exit 1;
		}
		unless (-r $ARGV[0]) {
			print "problem opening file ($ARGV[0]): permission denied\n";
			exit 1;
		}

		open $file, '<', $ARGV[0] or ( print "problem opening file ($ARGV[0]): $!\n" && exit 1 );
	}

	my $nomailtofatal = 1 unless (-t \*STDIN);
	unless ($file) {
		print "$ARGV[0]: $!\n";
		exit 1;
	}
	my @lines = <$file>;

	close $file;

	unless (&checkForMailto(@lines)) {
		if ($nomailtofatal) {
			print "Fatal error: no MAILTO= line found in input!\n";
			print "http://wiki.dreamhost.com/Crontab#MAILTO_variable_requirement for more information!\n";
			exit 1;
		}
		my $mailto = &getMailto;
		unshift @lines, ("MAILTO=\"$mailto\"");
	}

	my $ret = &newCrontab($options{u}, @lines);
	exit $ret;
}

sub getMailto {
	print "It looks like you don't have a MAILTO line in your crontab file\n";
	print "For performance reasons we ask that you specify an address where\n";
	print "cronjob output will be delivered.  If you do not wish to receive\n";
	print "cronjob output, simply press enter and cronjob output will not be\n";
	print "mailed to you.\n";
	print "\n";
	print "For more information regarding this, please visit:\n";
	print "http://wiki.dreamhost.com/Crontab#MAILTO_variable_requirement\n";
	print "\n";
	while (1) {
		print "Where would you like cronjob output delivered? (leave blank to disable)\n";
		print ": ";
		my $email = <STDIN>;
		chomp $email;
		print "\n";
		if ($email) {
			print "cronjob output will be emailed to $email\n";
		} else {
			print "cronjob output will not be emailed to you\n";
		}

		print "confirm? (y/N): ";
		my $confirm = <STDIN>;
		chomp $confirm;
		if ($confirm =~ /^y(es)?$/i) {
			return $email;
		}

	}

	die "not sure how we got here, but whatever, we're dying!";
}
			
sub newCrontab {
	my ($user, @lines) = @_;	

	my $pid;
	die "Cant fork: $!" unless defined($pid = open(KID, "|-"));
	if ($pid) { #parent!
		foreach my $line (@lines) {
			chomp $line;
			print KID "$line\n";
		}
		close KID;
		return $?;
	} else {
		exec ($crontab, ($user ? ('-u', $user) : ()), '-');
	}
}


sub safe_exec_with_output {
	my @args = @_;
	my $pid;
	die "Can't fork: $!" unless defined($pid = open(KID, "-|"));
	if ($pid) { # parent!
		my @output;
		while (<KID>) {
			chomp;
			push @output, $_;
		}
		close KID;
		return @output;
	} else {
		$ENV{PATH} = "/usr/bin";
		close STDERR;
		open STDERR, "> /dev/null";
		exec(@args);
	}
}

sub checkForMailto {
	my @lines = @_;
	my $u = shift;

	foreach my $line (@lines) {
		next if $line =~ /^\s*$/;
		next if $line =~ /^\s*#/;
		
		# return true if we find a MAILTO= before any crontab lines
		return 1 if ($line =~ /^MAILTO\s*=\s*["']?.*["']?$/);

		# return false if we find a crontab line.  because we haven't found a MAILTO line yet
		return 0 if ($line =~ /^\s*[\*\d\@]/);
	}
}
