1

I'm not sure if this is an XY problem, but I have to aupport a horrible piece of software that won't talk to modern mail servers properly. It can only do SMTP and insists on providing AUTH ... even when the relay server does not offer or require it, sets the from address based on the username despite being separate fields in the protocol, and it will simply disconnect if it doesn't get 250-AUTH PLAIN ... in the server's response to EHLO. There are many unresolved complaints about this software online so I know the vendor will be of no help. I tried adding sasl auth to the SMTP server (Postfix) but did not have any success. I could not get it to verify credentials, and I fear that they will be passed on to the relayhost which will reject them. I couldn't find a way to get Postfix to ignore sent credentials either.

To try to get around all this, I wrote a basic script that listens on its own port, and proxies the SMTP communication, validating some arbitrary credentials during the process:

#!/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

I run ./smtp_proxy 9025 and it works except the second nc immediately connects to the real SMTP server and begins a transaction which will time out:

$ ./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

I was hoping I could somehow delay the second nc invocation until it receives data from the pipeline, maybe by wrapping it in a function or something. socat seemed to do this on an unmodified connection:

socat -v tcp4-l:9025,crlf tcp4:127.0.0.1:25,crlf

...but as soon as I try to modify the streams by putting it in a pipeline, it does the same thing as nc, immediate connection to the SMTP server. (Replace the ncs above with socat TCP4-LISTEN:$1,crlf - and socat - TCP4:127.0.0.1:25,crlf respectively.)

Walf
  • 1,254
  • 1
  • 14
  • 19
  • 1
    Your best option might be stick one of the options form https://unix.stackexchange.com/q/33049/70524 before `nc` (e.g., `... | { until read -t 0; do sleep 1; done; nc ...; } | ...`, though `read -t 0` is unreliable, so use one of the other options) – muru Mar 05 '21 at 08:39
  • Thanks, @muru. With your pointer, I was able to do exactly what I was trying to, even though it wasn't ultimately successful. – Walf Mar 06 '21 at 08:24
  • Got sasl working. – Walf Mar 09 '21 at 03:21

1 Answers1

0

Based on some of the answers @muru linked to, plus a more general one about overcoming read returning false when input doesn't end with a newline, I came up with a function that actually does delay starting a program until it receives input:

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
}

To prove that it works, see the following test which shows both the initial statement group and the until_input start in parallel as expected, but delayed task (here cat) does not start until input does.

{ echo wait >&2; sleep 2; echo go >&2; echo -n pipe$'\n'it baby; } | until_input cat

Allthough this seems to be an okay solution for the general problem of awaiting input, unfortunately, it did not solve my problem. I put until_input nc -4C 127.0.0.1 25 in the pipeline, then discovered what I'd need is for the second netcat to start as soon as someone connects to the first, before any line is sent, because the server responds first in SMTP. I guess I'll just have to debug the sasl with the help of socat.

Walf
  • 1,254
  • 1
  • 14
  • 19