frater |
10-03-2010 08:03 AM |
relaying SMTP-server using authentication on a remote server using IMAP
I was given the assignment to enable our outgoing SMTP-server to be used for SMTP-clients not within our subnet. This SMTP-server is already relaying for all our customers using their source IP as a credential. A normal ISP's outgoing SMTP-server.
SMTP using a login and password on port 587 is something Sendmail supports.
After some investigating I found out that SASL was the way to go. A SASL service needs to be installed and sendmail can be configured to use that. SASL itself can be configured to use LDAP and some other methods, but I chose for the method 'rimap' (remote IMAP).
SASL would then issue a login to an IMAP-server and login to it and if the IMAP-server would allow it then it would give the Mail Agent (sendmail) that same answer.
This was almost what I wanted. On our net we have several servers and there's no way to configure sasl to use more than 1 IMAP-server. That's why I wrote a little shell script which behaves as an IMAP-server and would then find out which server it had to go to. If that server turns out to be one of ours it would login using IMAP and ask it there.
It finds out the IP of the IMAP-server by doing some A-record lookups. It will try mail.<domain>, pop.<domain> and then <domain>. If this doesn't result into an IP within our subnet it will fail.
I used xinetd to listen to port 143.
Configuring sasl & sendmail is not within the scope of this thread, but if someone needs some help...
feedback and comments about security are appreciated.
cat /etc/xinetd.d/imap
Code:
service imap
{
socket_type = stream
server = /usr/local/sbin/imap
only_from = localhost
protocol = tcp
user = root
group = root
wait = no
disable = no
log_on_success = HOST DURATION EXIT
log_on_failure = HOST
}
# cat /usr/local/sbin/imap
Code:
#!/bin/bash
export PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin
PASSFILE=/var/log/imap.passwd
LOGFILE=/var/log/imap.log
export TTL=3600
export SERVER=
FAILED_ALLOWED=50
CURSECONDS=`date +%s`
ISODAY=`date '+%Y%m%d'`
TABCHAR=`echo -e '\t'`
CLIENT=saslauthd
LOGGING=1
DEBUG=1
write_log () {
[ ${LOGGING} = 0 ] || echo "`date '+%b %d %H:%M'` imapd $*" >>${LOGFILE}
}
login_success () {
write_log "${CLIENT} OK LOGIN completed."
echo "${CLIENT} OK LOGIN completed."
exit 0
}
login_failed () {
write_log "${CLIENT} NO Login failed."
echo "${CLIENT} NO Login failed."
exit 1
}
no_login () {
write_log "${CLIENT} NO Login command."
echo "${CLIENT} OK."
exit 0
}
check_domain () {
TTL=3600
SERVER=`host -tA "$1." | grep -o 'has address .*' | awk '{print $3}'`
[ -z "${SERVER}" ] && return 1
[ -z "`which dig 2>/dev/null`" ] || TTL=`dig $1 | grep -A10 'ANSWER SECTION' | grep -B10 'AUTHORITY SECTION' | grep "IN${TABCHAR}A" | head -n1 | awk '{print $2}' | tr -cd '[0-9]'`
[ -z ${TTL} ] && TTL=3600
[ ${DEBUG} = 0 ] || write_log "Server found using domain $1.: ${SERVER} (${TTL})"
return 0
}
if [ -z "`which nc 2>/dev/null`" ] ; then
write_log "nc (netcat) is not installed."
echo "imap NO netcat (nc) installed"
exit 1
fi
trap "" 2 3 24
echo "* OK IMAP4rev1" ; read -t 120
if [ "$REPLY" ]; then
[ ${DEBUG} -gt 1 ] && write_log "RAW input: '`echo "${REPLY}" | tr -cd '[ -~]'`'"
echo "${REPLY}" | grep -qi "LOGIN " || no_login
CLIENT=`echo "${REPLY}" | awk '{print $1}'`
EMAIL=`echo "${REPLY}" | awk '{print $3}' | tr -cd '[#-~]'`
PASSWORD=`echo "${REPLY}" | awk '{print $4}' | tr -cd '[#-~]'`
if ! echo "${EMAIL}" | grep -q '@' ; then
write_log "Email: \"${EMAIL}\""
login_failed
fi
PASSWORD64="`echo -n "${PASSWORD}" | base64`"
[ ${DEBUG} -gt 1 ] && write_log "Email: \"${EMAIL}\" Password: \"${PASSWORD64}\""
[ -f ${PASSFILE} ] || touch ${PASSFILE}
[ -f ${PASSFILE}.failed ] || touch ${PASSFILE}.failed
PASSLINE="`grep "^${EMAIL}" ${PASSFILE}`"
if [ ! -z "${PASSLINE}" ] ; then
TIME=`echo "${PASSLINE}" | awk '{print $2}'`
if [ ${CURSECONDS} -lt ${TIME} ] ; then
PASSRESULT=`echo "${PASSLINE}" | awk '{print $3}'`
if [ "${PASSRESULT}" == "${PASSWORD64}" ] ; then
[ ${DEBUG} = 0 ] || write_log "Email: \"${EMAIL}\" authenticated from cache"
login_success
fi
fi
fi
# Check if someone authenticated today
OCCURENCE=0
FAILLINE="`grep "^${ISODAY} ${EMAIL}" ${PASSFILE}.failed`"
if [ ! -z "${FAILLINE}" ] ; then
OCCURENCE=`echo "${FAILLINE}" | awk '{print $3}' | tr -cd '[0-9]'`
if [ ${OCCURENCE} -gt ${FAILED_ALLOWED} ] ; then
sleep 5
# If someone has already succesfully authenticated, don't do a new lookup but just fail
# It's already an exceptional situation which can in worst case take 24 hours
if [ ! -z "${PASSLINE}" ] ; then
OCCURENCE=$(( ${OCCURENCE} + 1 ))
sed -i -e "s/^${ISODAY} ${EMAIL}.*/${ISODAY} ${EMAIL} ${OCCURENCE} ${PASSWORD}/" ${PASSFILE}.failed
login_failed
fi
fi
fi
DOMAIN=`echo "${EMAIL}" | awk -F@ '{print $2}'`
echo "${DOMAIN}" | grep -q '\.' || login_failed
if ! check_domain "mail.${DOMAIN}" ; then
if ! check_domain "pop.${DOMAIN}" ; then
check_domain "${DOMAIN}"
fi
fi
[ -z "${SERVER}" ] && login_failed
# Only for our servers
if ! echo "${SERVER}" | grep -q "89\.250\.1[789]" ; then
write_log "Foreign address: \"${EMAIL}\" wanted to authenticate (${SERVER})"
login_failed
fi
TIME_VALID=$(( ${CURSECONDS} + ${TTL} ))
# if imaplogin ${SERVER} ${EMAIL} ${PASSWORD} >/dev/null ; then
if echo -e "${CLIENT} LOGIN \"${EMAIL}\" \"${PASSWORD}\"" | nc -i2 ${SERVER} 143 | grep -q "${CLIENT} OK" ; then
if grep -q "^${EMAIL}" ${PASSFILE} ; then
sed -i -e "s/^${EMAIL}.*/${EMAIL} ${TIME_VALID} ${PASSWORD64} ${SERVER} ${REMOTE_HOST}/" ${PASSFILE}
else
echo "${EMAIL} ${TIME_VALID} ${PASSWORD64} ${SERVER} ${REMOTE_HOST}" >>${PASSFILE}
fi
login_success
else
if grep "^${ISODAY} ${EMAIL}" ${PASSFILE}.failed ; then
OCCURENCE=$(( ${OCCURENCE} + 1 ))
sed -i -e "s/^${ISODAY} ${EMAIL}.*/${ISODAY} ${EMAIL} ${OCCURENCE} ${PASSWORD}/" ${PASSFILE}.failed
else
echo "${ISODAY} ${EMAIL} 1 ${PASSWORD}" >>${PASSFILE}.failed
fi
login_failed
fi
fi
login_failed
|