Next: , Previous: , Up: sendmail   [Contents][Index]


A.2 Just send an email message

The script below can be found in examples/sendmail-plain.sh. It just sends a hard–coded email message, from a hard–coded address to a hard–coded address. It makes no use of MBFL.

Function: main

Drive the script controlling the SMTP protocol. It should be obvious what it does once we understand the following functions.

Function: open_session hostname

Open a connection to the SMTP server using a fake device that Bash gives us as interface to the network. For the localhost, it ends up being:

/dev/tcp/localhost/25

where 25 is the TCP port which is officially assigned to the SMTP service. To open the connection we use the idiom:

exec 3<>/dev/tcp/localhost/25

which means: open a read and write connection to the selected hostname, using file descriptor number 3. There is nothing special in number 3, it is just the first free file descriptor number after 0 (standard input), 1 (standard output) and 2 (standard error).

The line:

trap 'exec 3<&-' EXIT

means: close file descriptor 3 whenever the script terminates. This is redundant in such a simple script, it is there for completeness.

Function: send string
Function: send template [opt …]

Send a string to the SMTP server. Use printf() to format the string template with the optional arguments, then write the resulting string to file descriptor 3. The string written out is terminated with the sequence \r\n as mandated by the SMTP protocol.

Function: read_and_send_message

Read an email message from stdin line by line (newline terminator), and rewrite it to file descriptor 3 terminating each line with the sequence carriage return/line feed. With the exception of the terminating sequence, the lines are left unchanged.

Function: recv expected_code

Read a line (a sequence of characters up until the first \n) from file descriptor 3. The line is interpreted as a message from the SMTP server: the first three characters are a numeric code. If the code is different from expected_code, raise an error.

#! /bin/bash
#
# Part of: Marco's Bash Functions Library
# Contents: example script to send email
# Date: Thu Apr 23, 2009
#
# Abstract
#
#       This  script  just  sends  a  hardcoded  email  message  from  a
#       hardcoded address  to a hardcoded  address.  It makes no  use of
#       MBFL.
#
#         The purpose of this script is  to understand how to handle the
#       SMTP protocol.
#
# Copyright (c) 2009, 2010, 2018 Marco Maggi <marco.maggi-ipsu@poste.it>
#
# The author hereby grants  permission to use, copy, modify, distribute,
# and  license this  software  and its  documentation  for any  purpose,
# provided that  existing copyright notices  are retained in  all copies
# and that  this notice  is included verbatim in any  distributions.  No
# written agreement, license, or royalty  fee is required for any of the
# authorized uses.  Modifications to this software may be copyrighted by
# their authors and need not  follow the licensing terms described here,
# provided that the new terms are clearly indicated on the first page of
# each file where they apply.
#
# IN NO  EVENT SHALL THE AUTHOR  OR DISTRIBUTORS BE LIABLE  TO ANY PARTY
# FOR  DIRECT, INDIRECT, SPECIAL,  INCIDENTAL, OR  CONSEQUENTIAL DAMAGES
# ARISING OUT  OF THE  USE OF THIS  SOFTWARE, ITS DOCUMENTATION,  OR ANY
# DERIVATIVES  THEREOF, EVEN  IF THE  AUTHOR  HAVE BEEN  ADVISED OF  THE
# POSSIBILITY OF SUCH DAMAGE.
#
# THE  AUTHOR  AND DISTRIBUTORS  SPECIFICALLY  DISCLAIM ANY  WARRANTIES,
# INCLUDING,   BUT   NOT  LIMITED   TO,   THE   IMPLIED  WARRANTIES   OF
# MERCHANTABILITY,    FITNESS   FOR    A    PARTICULAR   PURPOSE,    AND
# NON-INFRINGEMENT.  THIS  SOFTWARE IS PROVIDED  ON AN "AS  IS" BASIS,
# AND  THE  AUTHOR  AND  DISTRIBUTORS  HAVE  NO  OBLIGATION  TO  PROVIDE
# MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#

PROGNAME=${0##*/}

function main () {
    local HOSTNAME=localhost
    local SMTP_PORT=25
    local FROM_ADDRESS=marco@localhost
    local TO_ADDRESS=root@localhost
    local LOGGING_TO_STDERR=yes

    open_session "$HOSTNAME"
    recv 220
    send 'HELO %s' 127.0.0.1
    recv 250
    send 'MAIL FROM:<%s>' "$FROM_ADDRESS"
    recv 250
    send 'RCPT TO:<%s>' "$TO_ADDRESS"
    recv 250
    send %s DATA
    recv 354
    print_message | read_and_send_message
    send %s .
    recv 250
    send %s QUIT
    recv 221
}
function print_message () {
    local LOCAL_HOSTNAME DATE MESSAGE_ID MESSAGE
    LOCAL_HOSTNAME=$(hostname --fqdn) || exit 2
    DATE=$(date --rfc-2822) || exit 2
    printf -v MESSAGE_ID '%d-%d-%d@%s' \
        $RANDOM $RANDOM $RANDOM "$LOCAL_HOSTNAME"
    MESSAGE="Sender: $FROM_ADDRESS
From: $FROM_ADDRESS
To: $TO_ADDRESS
Subject: demo from $PROGNAME
Message-ID: <$MESSAGE_ID>
Date: $DATE

This is a text demo from the $PROGNAME script.
--\x20
Marco
"
    printf "$MESSAGE"
}
function open_session () {
    local HOSTNAME=${1:?}
    local DEVICE
    printf -v DEVICE '/dev/tcp/%s/%d' "$HOSTNAME" $SMTP_PORT
    exec 3<>"$DEVICE"
    trap 'exec 3<&-' EXIT
}
function recv () {
    local EXPECTED_CODE=${1:?}
    local line=
    IFS= read -t 5 line <&3
    if ((127 < $?))
    then
        printf '%s: connection timed out\n' "$PROGNAME" >&2
        exit 2
    fi
    if test "$LOGGING_TO_STDERR" = yes
    then printf '%s log: recv: %s\n' "$PROGNAME" "$line"
    fi
    if test "${line:0:3}" != "$EXPECTED_CODE"
    then
        send '%s' QUIT
        # It is cleaner to wait for the reply from the server.
        IFS= read -t 5 line <&3
	if ((127 < $?))
	then
            printf '%s: connection timed out\n' "$PROGNAME" >&2
            exit 2
        fi
        if test "$LOGGING_TO_STDERR" = yes
	then printf '%s log: recv: %s\n' "$PROGNAME" "$line"
	fi
        exit 2
    fi
}
function send () {
    local template=${1:?}
    shift
    local line
    printf -v line "$template" "$@"
    printf '%s\r\n' "$line" >&3
    if test "$LOGGING_TO_STDERR" = yes
    then printf '%s log: sent: %s\n' "$PROGNAME" "$line"
    fi
}
function read_and_send_message () {
    local line
    local -i count=0
    while IFS= read line
    do
        if test "${line:0:1}" = '.'
        then printf '.%s\r\n' "$line" >&3
        else printf  '%s\r\n' "$line" >&3
        fi
        let ++count
    done
    if test "$LOGGING_TO_STDERR" = yes
    then printf '%s log: sent message (%d lines)\n' "$PROGNAME" $count
    fi
}

main

### end of file

Next: , Previous: , Up: sendmail   [Contents][Index]

This document describes version 3.0.0-devel.0 of Marcos Bash Functions Library.