perl - 在 Perl 中,是否可以在 close(SENDMAIL) 之前杀死 open(SENDMAIL, "|$sendmail")

标签 perl

我需要破解一个使用 open(SENDMAIL, "|$sendmail")close(SENDMAIL) 的旧系统。流打开后是否可以停止发送电子邮件?在这种情况下,如果发现一些意想不到的垃圾内容。

我试过了,但没有成功:

$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);

即使循环达到 $needToAbort === true,电子邮件仍然会发出。我能找到的最好的解释是,kill 9, $pid,只是强行关闭流,而不是真正杀死它。

为了验证 $pid 是否存在,我尝试添加到 if:

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

使用日志记录,有时 $pid 似乎存在,有时不存在。系统使用perl 5,版本16。

问题:这可能吗?我该如何编辑代码来阻止发送电子邮件?

最佳答案

命令 $sendmail 似乎没有直接启动 sendmail 程序,因此 $pidopen< 返回 不是 sendmail 的(而是 shell 的?)。

找到 sendmail 进程本身的 PID,kill 应该可以工作。 (或者考虑杀死整个进程组,请参阅结尾)。

您可以使用Proc::ProcessTable,而不是通过手动解析ps来实现这一点。

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

在我的系统上,CMD 字段以 sendmail 开头,请根据您的系统进行调整。如果可能有多个 sendmail 进程(很有可能),您需要进行更彻底的分析。

由于您需要将其从水中吹出,因此我认为无法修改其以下打印内容以进行检查。 (否则你可以用更简洁的方式解决这个问题。)

那么您必须安装SIGPIPE的信号处理程序,否则程序将在下次尝试打印到该文件句柄时终止,因为它将收到SIGPIPE 其处置是终止。

另一个解决方案是将 sendmail 处理包装在 Expect 中,它会设置一个伪终端,以便您可以在需要时发送 Ctrl-C。 (它自己的 hard_close 方法也在我的测试中完成了这项工作。)但是为此,应该修改打印语句,因此这里可能不可行。


更详细一点。澄清该命令是:/usr/lib/sendmail -f$sender -t

模块的对象(上面的$pt)有很多进程表字段,由$pt->fields列出,其描述见 "stub module" 。我发现打印并查看所有感兴趣的对象会提供更多信息。对于此目的可能有帮助的有 execcwd 和各种 id

如何准确识别进程取决于系统的详细信息,但一种方法是查看命令行详细信息。

上面的例子做了一些扩展

$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" }
};

根据确切的短语“-f$sender”检查命令行的第二个字段,可以使用正则表达式而不是eq来重新分析。查看为上述所有进程打印的命令行并根据需要进行调整。如果出现问题,请打印出任何包含 'sendmail' 的内容。


另一个选择是杀死 process group : kill 9, -$pid (注意减号)。这应该捕获 sendmail 进程本身,但当然要确保您知道什么被吹走了。

补充一点,我怀疑您是否需要使用SIGKILL (9)。一旦找到正确的 pid,SIGTERM(在我的系统上为 15,请参阅 man 7 signal)可能就足够好了,还有更好的。

最后,进程可能会受到操作系统的束缚并处于不可中断的状态,特别是在某些 I/O 操作中。然而,这在这里似乎不太可能,我首先尝试上面的两种方法。

关于perl - 在 Perl 中,是否可以在 close(SENDMAIL) 之前杀死 open(SENDMAIL, "|$sendmail"),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43794371/

相关文章:

perl - 如何在perl中通过变量值按名称调用方法?

perl - Web 上的官方 Perl 文档是什么?

multithreading - 在线程之间转发/传递文件描述符

c - 如何在 Perl 中通过 Inline::C 使用 VTD-XML?

perl - 如何检索具有重载字符串化的对象的数字地址

用于 API 的 perl 测试套件

Perl - 为什么需要指定使用 'state' 功能?

字符串的正则表达式可选择用引号括起来

regex - 有没有更好的方法来删除 perl 中字符串数组的字符串?

长名称的 Perl 函数命名约定