2

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?

Out of Control
  • 456
  • 1
  • 6
  • 21
  • 1
    It's not `close SENDMAIL` that sends out the email, it's the `print SENDMAIL ...`. You need to intervene before that happens. – mob May 05 '17 at 02:13
  • Verified that we are definitely intervening before print SENDMAIL. – Out of Control May 05 '17 at 02:19
  • 2
    You might be killing a shell, depending on the contents of `$sendmail`. – ikegami May 05 '17 at 02:52
  • 4
    Can't you wait until you know you want to send the mail before opening the pipe? You could `push` strings onto an array instead of `print`ing them to the pipe. Then you can just discard the contents of the array if you find you don't want it sent. – Borodin May 05 '17 at 03:51
  • Or make sure you find the pid of the actual sendmail process, then killing it will work. – zdim May 05 '17 at 04:02
  • @zdim Contents of $sendmail appear to be "/usr/lib/sendmail -f\$sender -t" – Out of Control May 05 '17 at 11:48
  • @Borodin as much as this should be rewritten and changed, it's legacy code that does work, and is secure, and with my limited knowledge of perl, I'd prefer not to go down that rabbit hole. – Out of Control May 05 '17 at 11:50

1 Answers1

2

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.

Community
  • 1
  • 1
zdim
  • 64,580
  • 5
  • 52
  • 81
  • $sendmail appears to be "/usr/lib/sendmail -f\$sender -t", so that in the end `open (SENDMAIL, "|/usr/lib/sendmail -f\$sender -t");` is what is being run. You have waitpid... which more research suggests that maybe $pid is not getting killed of before I reach close()? More testing now. – Out of Control May 05 '17 at 11:55
  • @OutofControl The form of `open` that is used `fork`'s a process and once we `kill` it we should by all means `wait` for it. The `waitpid` allows to wait for a particular process (by PID). Change the regex then, to match things that will identify the process on your system. I'll add a bit about that. – zdim May 05 '17 at 16:59
  • @OutofControl You need to kill the process before you reach prints to its filehandle. As soon as those happen it is in the hands of `postfix` (and `postdrop` I think) and they _will_ send whatever is in their queues and temp files. By killing "before close" (but after there were prints to the filehandle) we only mess up our process, but not stop the email. – zdim May 05 '17 at 17:01
  • If open() has already executed, but no prints, would I be able to kill the $pid and have no email sent? – Out of Control May 05 '17 at 17:56
  • @OutofControl Yes -- it would be a big surprise that `kill` fails. If `sendmail` is killed before any prints then there is no email, and in my tests nothing is sent to that recepient, no message. (You'll need to test on your system.) The only thing is to identify the right pid. (You can even print all processes with `sendmail` in their command, just to see what is going on.) Updated the answer, will edit more. Let me know what needs to be explained – zdim May 05 '17 at 18:01
  • @OutofControl I added a note on another option, to kill the process group by `kill 9, -$pid`. Also edited a litt.e – zdim May 05 '17 at 18:54
  • Sidetracked for a couple of days. I'll review this after the weekend when I can give it the proper attention. – Out of Control May 11 '17 at 18:11
  • @OutofControl Alright, thanks for the note. Once you get to it, perhaps first try with the _process group_ way (at the end), as it's simplest to do. Let me know how it goes. – zdim May 11 '17 at 18:17
  • Thanks for the detailed response. The issue in the end appears to be have been a conditional that would occasionally be true which resulted in a 'print' being pushed to sendmail. However, your solution allowed me to be sure I had the correct PID, which is a must have. – Out of Control May 17 '17 at 14:06