我不确定这是否是一个XY问题,但我必须支持一个可怕的软件,它无法正确地与现代邮件服务器通信。它只能执行 SMTP 并坚持提供,AUTH ...
即使中继服务器不提供或不需要它,根据用户名设置发件人地址,尽管在协议中是单独的字段,如果不进入,它将简单地断开250-AUTH PLAIN ...
连接服务器对 的响应EHLO
。网上有很多关于该软件的未解决的投诉,所以我知道供应商不会提供任何帮助。我尝试将 sasl auth 添加到 SMTP 服务器(Postfix),但没有成功。我无法让它验证凭据,我担心它们会被传递给relayhost
拒绝它们的人。我也找不到让 Postfix 忽略发送的凭据的方法。
为了尝试解决所有这些问题,我编写了一个基本脚本,用于侦听自己的端口,并代理 SMTP 通信,并在此过程中验证一些任意凭据:
#!/bin/bash
user=<<REDACTED from>>
pw=<<REDACTED password>>
function reset {
send_auth=1
authing=0
ok=1
p=/tmp/smtp_backpipe
if [[ -p $p ]]; then
# drain pipe
dd if=$p iflag=nonblock of=/dev/null 2>/dev/null
else
mkfifo $p
fi
}
function pw_check {
if [[ "${1%%[[:space:]]}" == "${auth%%[[:space:]]}" ]]; then
echo 235 2.7.0 Authentication successful > $p
else
echo 535 5.7.0 Authentication failed > $p
ok=0
fi
}
function changeo {
while (( ok )) && IFS= read -r line; do
case $1 in
in)
if (( authing )); then
>&2 printf '%s\n' "$line"
pw_check "$line"
authing=0
elif [[ "$line" =~ ^AUTH[[:space:]]+PLAIN([[:space:]]+([^[:space:]]+))?[[:space:]]*$ ]]; then
if [[ "${BASH_REMATCH[1]}" ]]; then
>&2 printf '%s\n' "$line"
pw_check "${BASH_REMATCH[2]}"
else
authing=1
echo 334 | tee -a /dev/stderr > $p
fi
else
printf '%s\n' "$line"
fi
;;
out)
if [[ ! "$line" =~ ^250[^[:alpha:]]+AUTH[[:space:]] ]]; then
printf '%s\n' "$line"
if (( send_auth )) && [[ "$line" =~ ^250[^[:alpha:]] ]]; then
send_auth=0
echo 250-AUTH PLAIN
fi
fi
;;
esac
if ! (( ok )); then
exit 1
fi
done
}
auth="`printf '\0%s\0%s' "$user" "$pw" | base64`"
i=0
while true; do
>&2 echo "$(( ++i ))"
reset
cat $p | tee -a /dev/stderr | netcat -4Clp $1 | changeo in | tee -a /dev/stderr | nc -4C 127.0.0.1 25 | changeo out > $p
done
我运行./smtp_proxy 9025
并且它工作正常,除了第二个nc
立即连接到真正的 SMTP 服务器并开始一个将超时的事务:
$ ./smtp_proxy 9025
1
220 <<REDACTED host>> ESMTP Postfix
EHLO [10.0.0.99]
250-<<REDACTED host>>
250-AUTH PLAIN
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-ENHANCEDSTATUSCODES
250-8BITMIME
250-DSN
250-SMTPUTF8
250 CHUNKING
AUTH PLAIN <<REDACTED auth>>
235 2.7.0 Authentication successful
MAIL FROM:<<<REDACTED from>>>
250 2.1.0 Ok
RCPT TO:<<<REDACTED to>>>
250 2.1.5 Ok
DATA
354 End data with <CR><LF>.<CR><LF>
From: <<REDACTED from>>
Subject: hi
To: <<REDACTED to>>
Content-Type: text/plain; charset=windows-1252; format=flowed
Content-Transfer-Encoding: 7bit
there
.
250 2.0.0 Ok: queued as F23DFE0A1B
QUIT
221 2.0.0 Bye
2
220 <<REDACTED host>> ESMTP Postfix
421 4.4.2 <<REDACTED host>> Error: timeout exceeded
我希望我可以以某种方式延迟第二次nc
调用,直到它从管道接收数据,也许可以通过将其包装在函数或其他东西中。socat
似乎在未修改的连接上执行此操作:
socat -v tcp4-l:9025,crlf tcp4:127.0.0.1:25,crlf
...但是一旦我尝试通过将其放入管道中来修改流,它就会执行与nc
立即连接到 SMTP 服务器相同的操作。 (将nc
上面的 s 分别替换为socat TCP4-LISTEN:$1,crlf -
和socat - TCP4:127.0.0.1:25,crlf
。)
答案1
根据 @muru 链接到的一些答案,加上一个关于克服read
输入不以换行符结尾时返回 false 的更一般的答案,我想出了一个实际上会延迟启动程序直到收到输入的函数:
function until_input {
>&2 echo waiting for input # debug only, remove this line
local line
local nl=0
local format='%s'
while IFS= read -r line; do
nl=1
format='%s\n'
break
done
>&2 echo done waiting # debug only, remove this line
if (( nl )) || [[ "$line" ]]; then
>&2 echo was input # debug only, remove this line
{ printf "$format" "$line"; cat; } | "$@"
fi
}
为了证明它的工作原理,请参阅以下测试,该测试显示了初始语句组和until_input
按预期并行启动,但延迟任务(此处cat
)直到输入启动后才会启动。
{ echo wait >&2; sleep 2; echo go >&2; echo -n pipe$'\n'it baby; } | until_input cat
尽管这似乎是等待输入的一般问题的一个不错的解决方案,但不幸的是,它并没有解决我的问题。我放入until_input nc -4C 127.0.0.1 25
管道,然后发现我需要的是,netcat
在发送任何行之前,一旦有人连接到第一个管道,第二个管道就启动,因为服务器首先在 SMTP 中响应。我想我只需要在socat
.