Email Send Script Implemented in Expect

Over the years I’ve happened upon numerous cases where I’ve needed to send emails within a shell script on one of my linux devices – most recently for my home automation machine running on a seagate dockstar. At times one or another sendmail binary worked, but I tend to prefer minimalist solutions where disk footprint is kept to a minimum, and where I have complete control of the functionality without having to compile something. After much fiddling and internet searchification I ran across Peter Vibert’s expect script to send a test message, and converted it to use a mime formatted input file.

#!/usr/bin/expect
#
# sends a properly formatted file to smtp server
# usage: send_email
#
# blatantly copied from Peter Vibert’s expect script:
# http://www.petervibert.com/posts/01-11-09-expect-smtp.html
#

if {$argc<3} {
send_user "sends mime formatted message file to smtp server using telnet\n"
send_user "usage: send_email mailserver port message_file\n"
exit
}

set mailserver [lrange $argv 0 0]
set portno [lrange $argv 1 1]
set cfile [lrange $argv 2 2]

set fp [open "$cfile" r]
set content [read $fp]
set hostname [exec hostname]

# extract the from address from message file
# must be in one of two forms:
# From: "Recipient Name"
# or
# From: recipient@foo.com
set from [exec grep "From:" "$cfile"]
set quoted [string match "?*<*@*.*>" "$from"]
if [expr $quoted > 0 ] {
set from [exec echo "$from" | cut -d< -f2 | tr -d '>']
} else {
set from [exec echo "$from" | cut -d: -f2 | tr -d \[:space:\]]
}

# extract the to address – same as from (see above)
set to [exec grep "To:" "$cfile"]
set quoted [string match "?*<*@*.*>" "$to"]
if [expr $quoted > 0 ] {
set to [exec echo "$to" | cut -d< -f2 | tr -d '>']
} else {
set to [exec echo "$to" | cut -d: -f2 | tr -d \[:space:\]]
}

spawn telnet $mailserver $portno
expect “failed” {
send_user “$mailserver: connect failed\n”
exit
} “2?? *” {
} “4?? *” {
exit
} “refused” {
send_user “$mailserver: connect refused\n”
exit
} “closed” {
send_user “$mailserver: connect closed\n”
exit
} timeout {
send_user “$mailserver: connect to port $portno timeout\n”
exit
}
send “HELO $hostname\r”
expect “2?? *” {
} “5?? *” {
exit
} “4?? *” {
exit
}
send “MAIL FROM: <$from>\r”
expect “2?? *” {
} “5?? *” {
exit
} “4?? *” {
exit
}
send “RCPT TO: <$to>\r”
expect “2?? *” {
} “5?? *” {
exit
} “4?? *” {
exit
}
send “DATA\r”
expect “3?? *” {
} “5?? *” {
exit
} “4?? *” {
exit
}
log_user 0
send — “$content”
set timeout 1
expect “$content”
close $fp
send “.\r”
expect “.\r”
expect “2?? *” {
} “5?? *” {
exit
} “4?? *” {
exit
}
send_user “$expect_out(buffer)”
send “QUIT\r”
exit

While there may be more elegant ways to extract the to and from addresses without resorting to grep and other shell utilities, this fills the need and got me back onto working on more important functions quickly. A more thorough programmer would take the time to add better error handling to check for dependencies, but I’m not a thorough programmer. So you need telnet, grep, and tr installed in order for this to work. If you want to send binary attachments such as images you will also need openssl to do the base64 encoding. Below is and example of MIME multipart message and the openssl command to generate the base64 attachment. Note: I’m pretty sure the extra linefeeds before the boundary delimiters and after the boundary headers are critical.

From: “Home Control”
Date: Fri Jun 1 15:46:10 UTC 2012
Subject: Solar Power Data
To: “Recipient Name”
MIME-Version: 1.0
Content-type: multipart/mixed; boundary=”asdfjkl”

This is a multi-part message in MIME format.

–asdfjkl

Content-type: text/plain

Battery_Volts.senseMax=12.90
PV_Amps.senseMax=2.361
PV_Watts.senseMax=29.965
Bulk_Amps.senseMax=1.518
Bulk_Watts.senseMax=18.407
Load_Amps.senseMax=0.817
Load_Watts.senseMax=10.393
Battery_Volts.senseMin=11.95
PV_Amps.senseMin=0.000
PV_Watts.senseMin=0.000
Bulk_Amps.senseMin=0.000
Bulk_Watts.senseMin=0.000
Load_Amps.senseMin=0.631
Load_Watts.senseMin=8.043

–asdfjkl
Content-type: application/octet-stream
Content-disposition: attachment; filename=solar.png;
Content-transfer-encoding: base64

iVBORw0KGgoAAAANSUhEUgAAAyYAAAHhCAIAAAAUJz1AAAAABmJLR0QA/wD/AP+g
vaeTAAAgAElEQVR4nOydfXgU5bn/nw2JoGSJWSAhFJZ2IwUJL77VgC9lI1SpFkqL
1pefBXosbWh9AUqLFTUJgklqOf5EObankKs9XLUUFSUhNRU8STUlonDCi8CP6q4k
CoSX3SRnkQQE9vfHJE8mM7uz8zw7z+7M5Pu5uOZaZu797nfuTGae3HPPs47GxkYC
AAAAAACE8b//+7+pyfYAAAAAAGBn2traPv30064h11VXXZVcNwAAAAAAtuTVV18l
hKQk2wYAAAAAgP3BkAsAAAAAQDgYcgEAAAAACAft8wAAAAAA8VJTU6NYM2PGDPl/

–asdfjkl–

The base64 blob has been truncated, so if you copy/paste this into a file and send it the attachment will be trashed.

To generate the base64 blob with openssl:

openssl enc -base64 -in solar.png >> file.to.send

If you’re doing this in a script, you’ll want to do so after you written the attachment boundary headers, and before the closing boundary delimiter (–asdfjkl– in my example).

This entry was posted in Home Control, Seagate Dockstar. Bookmark the permalink.

Leave a Reply