LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (https://www.linuxquestions.org/questions/programming-9/)
-   -   relaying SMTP-server using authentication on a remote server using IMAP (https://www.linuxquestions.org/questions/programming-9/relaying-smtp-server-using-authentication-on-a-remote-server-using-imap-835919/)

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


acid_kewpie 10-03-2010 08:23 AM

Why not just enable SMTP authentication?? This seems really strange and OTT from what I understand of your situation.

frater 10-04-2010 01:55 AM

Quote:

Originally Posted by acid_kewpie (Post 4116300)
Why not just enable SMTP authentication?? This seems really strange and OTT from what I understand of your situation.

I am using SMTP-authentication on Sendmail (should also work with Exim4 and Postfix).
It authenticates using Cyrus-SASL which is already builtin for sendmail.
http://www.falkotimme.com/howtos/sendmail_smtp_auth_tls

sasl supports several methods and will pass the result to the MTA.
The method I'm using is rimap (remote IMAP) which already is being used (I can only assume).

What I wrote is a fake IMAP-server which enables me to authenticate to more than 1 IMAP-server.
It will take the realm of the email-address (the domain) and then authenticates to that server.


All times are GMT -5. The time now is 09:03 AM.