Out of Control
Out of Control

Reputation: 479

In Perl, is it possible to kill an open(SENDMAIL, "|$sendmail") before close(SENDMAIL)

I'm needing to hack an old system that uses open(SENDMAIL, "|$sendmail") and close(SENDMAIL). Is it possible to stop the email from being sent once the stream is open? In this case, if some unexpected spammy content is discovered.

I've tried this with no luck:

$pid = open(SENDMAIL, "|$sendmail");
while (<>) {
    ....do lots of stuff in here....
    # Oops, we need to abort
    if ($needToAbort) {
        kill 9, $pid;
        exit(0);
    }
}
close(SENDMAIL);

Even when the loop hits $needToAbort === true, the email still goes out. Best explanation I can find is that kill 9, $pid, is only closing off the stream forcibly, not actually killing it.

In order to verify $pid exists, I'd tried add to the if:

if ($needToAbort) {
    $exists = kill 0, $pid;
    if ($exists) {
        kill 9, $pid;
        exit(0);
    }
}

Using logging, sometimes the $pid seems to exist and sometimes it doesn't. The system uses perl 5, version 16.

Question: Is it possible, and how would I edit my code to stop the email from being sent?

Upvotes: 2

Views: 328

Answers (1)

zdim
zdim

Reputation: 66883

It seems that the command $sendmail isn't starting the sendmail program directly so the $pid returned by open isn't sendmail's (but shell's?).

Find the PID of the sendmail process itself and kill should work. (Or consder killing the whole process group, see the end).

Instead of doing that by manually parsing ps you can use Proc::ProcessTable

use Proc::ProcessTable;

my $pid = open my $sm_fh, '|-', $sendmail or die "Can't open sendmail: $!";

my $pid_sm;
my $pt = Proc::ProcessTable->new();
foreach my $proc (@{$pt->table}) {
    if ($proc->cmndline =~ /^sendmail/) {  # adjust regex for your system
        $pid_sm = $proc->pid;
        say "Sendmail command-line: ", $proc->cmndline;
        say "Sendmail process pid:  ", $proc->pid;
    }   
}

kill 9, $pid_sm;
my $gone_pid = waitpid $pid_sm, 0;
say "Process $gone_pid is gone";

# need a handler for SIGPIPE for prints to $sm_fh further in code

On my system the CMD field starts with sendmail, adjust for how it is on yours. If there may be multiple sendmail processes, what is quite possible, you'll need a more thorough analysis.

Since you need to blow the thing out of the water I presume that its following prints can't be modified for a check. (Otherwise you could solve this in much cleaner ways.)

Then you must install a signal handler for SIGPIPE or the program will die at the next attempt to print to that filehandle, since it will get a SIGPIPE and its disposition is to terminate.

Another solution is to wrap sendmail handling in Expect, which sets up a pseudo-terminal so you can send a Ctrl-C when needed. (Its own hard_close method does the job in my tests, too.) But for this the printing statements should be modified so it may be a no-go here.


A little more detail. It was clarified that the command is: /usr/lib/sendmail -f$sender -t

The module's object ($pt above) has a lot of process table fields, listed by $pt->fields, with descriptions at its "stub module". I find it more informative to print and review them all for objects of interest. Some that may be helpful for this purpose are exec, cwd, and various ids.

How exactly to identify a process depends on details of the system, but one way is to look at the command line details.

The example above extended a bit

$SIG{PIPE} = sub { print "Got $_[0]\n" };  # or just $SIG{PIPE} = 'IGNORE';

my $pid_sm;
foreach my $proc (@{$pt->table}) {
    my $cmd = $proc->cmndline;
    next if $cmd !~ m{^/usr/lib/sendmail};
    if ( (split ' ', $cmd)[1] eq "-f$sender" ) {
        $pid_sm = $proc->pid;
        say "Our process $pid_sm: $cmd";
    }
    else { say "Some other sendmail: $cmd" }
}
warn "Didn't find our sendmail process" if not $pid_sm;

if ($needToAbort and $pid_sm) {
    kill 9, $pid_sm;
    my $gone_pid = waitpid $pid_sm, 0;
    if    ($gone_pid == -1) { say "No process $pid_sm" }
    elsif ($gone_pid ==  0) { say "Process $pid_sm still around?" }
    else                    { say "Process $gone_pid is gone" }
};

The second field of the command-line is checked against the exact phrase "-f$sender", what can be relexad by using regex instead of eq. Review command-lines printed for all processes above and adjust as needed. If there are problems print out anything that has 'sendmail' in it.


Another option is to kill the process group: kill 9, -$pid (note the minus). This should catch the sendmail process itself, but of course make sure you know what is getting blown away.

To add, I doubt that you need to use SIGKILL (9). Once the right pid is found the SIGTERM (15 on my system, see man 7 signal) may well be good enough, what is much nicer.

Finally, a process can get tied down by the OS and be in an uninterruptible state, in particular in some I/O operation. However, that doesn't seem likely here and I'd first try the two approaches above.

Upvotes: 2

Related Questions