我需要破解一个使用 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
程序,因此 $pid
由 open< 返回
不是 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" 。我发现打印并查看所有感兴趣的对象会提供更多信息。对于此目的可能有帮助的有 exec
、cwd
和各种 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/