How To Send MIME Attachments Using EEM

Document

Mon, 07/25/2016 - 12:29
Mar 31st, 2010

It is very easy to send email messages using EEM.  Often times, it is sufficient to simply include CLI output or other messages in the body of the email.  However, some output may be quite large, or you may have a need to send binary attachments (e.g. Embedded Packet Capture files).  In those cases, it would be better to use MIME encoding, and add the data as file attachments.  Here is a short proof-of-concept EEM script that demonstrates how to do this.

::cisco::eem::register_event_none

namespace import ::cisco::eem::*

namespace import ::cisco::lib::*

# First, create the headers and body of your message

set email_body_pre "Mailservername: $_email_server
From: $_email_from
To: $_email_to
Cc:
Subject: Email from Router $_router_name
MIME-Version: 1.0
Content-type: multipart/mixed; boundary=\"EEM_email_boundary\"
\n--EEM_email_boundary\n
\n--EEM_email_boundary
Content-Type: application/octet-stream
Content-Transfer-Encoding: Base64
Content-Disposition: attachment; filename=\"test.log\"\n\n"

# Then, read in the data you wish to attach.

if [catch {open "flash:test.log"} result] {
    error $result $errorInfo
}
set fd $result

# Encode that data using the built-in base64 library.
set email_b64 [::base64::encode [read $fd]]
close $fd

# Construct the final message with the headers, body, and MIME parts.

set email_body_mime "\n--EEM_email_boundary--"

set email_body [format "%s%s%s" $email_body_pre $email_b64 $email_body_mime]

# Send the email.

if [catch {smtp_send_email $email_body} result] {
    error $result $errorInfo
}

ideocisco Fri, 10/02/2015 - 11:54

Hi,

 

What if you need to send 2 attachments and add text in the body of the email?

 

Thanks,

Stéphane

Joe Clarke Fri, 10/02/2015 - 11:58

You just need to add more boundaries (i.e. more EEM_email_boundary sections).  The text body will come between the first two in $email_body_pre.

ideocisco Fri, 10/02/2015 - 12:02

Thanks for your fast response.

I am new to this, so please forgive my lack of knowledge.

Is this what you mean :

MIME-Version: 1.0
Content-type: multipart/mixed; boundary=\"EEM_email_boundary\"
\n--EEM_email_boundary\n
This is a test to appear in the body of the message
\n--EEM_email_boundary
Content-Type: application/octet-stream
Content-Transfer-Encoding: Base64
Content-Disposition: attachment; filename=\"test.log\"\n\n"
\n--EEM_email_boundary
Content-Type: application/octet-stream
Content-Transfer-Encoding: Base64
Content-Disposition: attachment; filename=\"test2.log\"\n\n"

Thanks,

Stéphane

ideocisco Fri, 10/02/2015 - 13:47

Hi,

Thanks this works perfectly nice.
Another question.
What if I wanted to attach text already in a variable. Is this possible?

Ex :
set email_attachment_1 "This is a test"
Content-Disposition: attachment; filename=\"Message.txt\"\n\n"
set email_b64 [::base64::encode [$email_attachment_1]]

Thanks,

Stéphane

ideocisco Fri, 10/02/2015 - 13:55

I get the following error :

Router#event manager run Test.tcl

invalid command name "
 Pool Tag: 21
 MAC Address                : AAAA.AAAA.AAAA
 No. of attempts to register: 0
 Unregister time            :
 Last register request time :
 Reason for state unregister:
         No registration request since last reboot/unregister

 Pool Tag: 22
 MAC Address                : BBBB.BBBB.BBBB
 No. of attempts to register: 0
 Unregister time            :
 Last register request time :
 Reason for state unregister:
         No registration request since last reboot/unregister

Router#"

    while executing
"$email_attachment_1"
    invoked from within
"if {$ata_down} {
  append email_pre "$msg"

  app..."
    (file "tmpsys:/eem_policy/Test.tcl" line 179)
Tcl policy execute failed: invalid command name "
 Pool Tag: 21
 MAC Address                : AAAA.AAAA.AAAA
 No. of attempts to register: 0
 Unregister time            :

 Last register request time :
 Reason for state unregister:
         No registration request since last reboot/unregister

 Pool Tag: 22
 MAC Address                : BBBB.BBBB.BBBB
 No. of attempts to register: 0
 Unregister time            :
 Last register request time :
 Reason for state unregister:
         No registration request since last reboot/unregister

 

$email_attachment_1 contains the following :

 Pool Tag: 21
 MAC Address                : AAAA.AAAA.AAAA
 No. of attempts to register: 0
 Unregister time            :
 Last register request time :
 Reason for state unregister:
         No registration request since last reboot/unregister

 Pool Tag: 22
 MAC Address                : BBBB.BBBB.BBBB
 No. of attempts to register: 0
 Unregister time            :
 Last register request time :
 Reason for state unregister:
         No registration request since last reboot/unregister

Joe Clarke Fri, 10/02/2015 - 14:16

You can't put brackets around your variable.  It should be:

set email_b64 [::base64::encode $email_attachment_1]

 

ideocisco Fri, 10/02/2015 - 15:40

Thank you Joseph.

You are truly the best contributer I have the please to chat with.

This is working like a charm.

Thanks,

Stéphane

ideocisco Sat, 10/03/2015 - 10:54

Hi,

When I try to have 2 attachments, I do not get the results expected. The first attachment is empty and the 2nd attachment contains the data of the 2 files.

Here is my code :

set email_pre "Mailservername: $_email_server
From: $_email_from
To: $_email_to
Cc:
Subject: Test 2 attachment
MIME-Version: 1.0
Content-type: multipart/mixed; boundary=\"EEM_email_boundary\"
\n--EEM_email_boundary\n
This is a test email with 2 attachments"
append email_pre "\n--EEM_email_boundary
Content-Type: application/octet-stream
Content-Transfer-Encoding: Base64
Content-Disposition: attachment; filename=\"test.log\"\n\n"

if [catch {open "flash:test.log"} result] {
    error $result $errorInfo
}
set fd $result
set email_b64 [::base64::encode [read $fd]]
close $fd

append email_pre "\n--EEM_email_boundary
Content-Type: application/octet-stream
Content-Transfer-Encoding: Base64
Content-Disposition: attachment; filename=\"test2.log\"\n\n"

if [catch {open "flash:test2.log"} result] {
    error $result $errorInfo
}
set fd $result
set email_b64_2 [::base64::encode [read $fd]]
close $fd

set email_body_mime "\n--EEM_email_boundary--"
set email_body [format "%s%s%s" $email_pre $email_b64 $email_b64_2 $email_body_mime]

if [catch {smtp_send_email $email_body} result] {
    error $result $errorInfo
}

Joe Clarke Sat, 10/03/2015 - 11:13

You need to have the boundaries between your attachments.  Appending another boundary to email_pre doesn't do you any good.  Best to gave email_pre be your initial headers and email body.  Then each attachment will start its boundary and MIME info.

ideocisco Sat, 10/03/2015 - 11:24

Hi,

Don't I have boudaries between my attachments :

append email_pre "\n--EEM_email_boundary
Content-Type: application/octet-stream
Content-Transfer-Encoding: Base64
Content-Disposition: attachment; filename=\"test.log\"\n\n"

append email_pre "\n--EEM_email_boundary
Content-Type: application/octet-stream
Content-Transfer-Encoding: Base64
Content-Disposition: attachment; filename=\"test2.log\"\n\n"

Joe Clarke Sat, 10/03/2015 - 11:27

No, because $email_pre is only included at the front of you email.  You construct the email as:

 

email_pre

email_b64

email_b64_2

email_body_mime

 

You need a boundary and MIME header before email_b64 and before email_b64_2.

ideocisco Sat, 10/03/2015 - 11:40

Hi,

So something like :
set email_pre2 "MIME-Version: 1.0
Content-type: multipart/mixed; boundary=\"EEM_email_boundary\"
\n--EEM_email_boundary\n
\n--EEM_email_boundary
Content-Type: application/octet-stream
Content-Transfer-Encoding: Base64
Content-Disposition: attachment; filename=\"test2.log\"\n\n"

set email_body_mime "\n--EEM_email_boundary--"
set email_body [format "%s%s%s%s%s%s" $email_pre $email_b64 $email_body_mime $email_pre2 $email_b64_2 $email_body_mime]

 

ideocisco Sat, 10/03/2015 - 11:35

Hi,

I am getting there.

The above code sends me three attachments.
The 2 log files and the Body message both in the message and as an attachment.

ideocisco Sat, 10/03/2015 - 12:35

Hi,

I finally understand.


It is working.

This is what I did :

 

Thanks a lot for your help.

set email_body [format "%s%s%s%s%s%s%s" $email_pre $email_body $email_mime_1 $email_b64 $email_mime_2 $email_b64_2 $email_body_mime]

Eric Roach Thu, 07/14/2016 - 08:10

Joe,

I have your script using file attachments working on ASR1002-X and an ISR4451.

I am not able to use the script with Catalyst 6880.

Version 15.2(1)SY1

The debugs did not seem to provide much additional informational, and I have used a very stripped down mime script with no attachment with same results. I did test the stripped down on ASR router and it worked. I am using the same SMTP server on the ISR4451 which works just fine.

event manager run basicsndmail.tcl
error connecting to mail server:
cannot connect to all the candidate mail servers
    while executing
"smtp_send_email $mail_msg"
    invoked from within
"$slave eval $Contents"
    (procedure "eval_script" line 7)
    invoked from within
"eval_script slave $scriptname"
    invoked from within
"if {$security_level == 1} {       #untrusted script
     interp create -safe slave
     interp share {} stdin slave
     interp share {} stdout slave
..."
    (file "tmpsys:/lib/tcl/base.tcl" line 50)
Tcl policy execute failed: error connecting to mail server:
cannot connect to all the candidate mail servers

Attachment: 
Joe Clarke Fri, 07/15/2016 - 04:58

Can the 6800 ping the SMTP server?  If you telnet from this switch to tcp/25 on the SMTP server and type "HELO Cat6800" where "Cat6800" is the hostname of the switch, what do you see?

Eric Roach Fri, 07/15/2016 - 08:11

Ping is good and I see "Unrecognized command" see below for results.


NET_MHO_LAN_6880#telnet 10.40.11.30 25
Trying 10.40.11.30, 25 ... Open
220 MCEXCHHUB02.mclane.mclaneco.com Microsoft ESMTP MAIL Service ready at Fri, 15 Jul 2016 09:56:51 -0500


Helo NET_MHO_LAN_6880
250 MCEXCHHUB02.mclane.mclaneco.com Hello [10.40.11.7]

Edited this post b/c I mistyped Helo command.

Eric Roach Fri, 07/15/2016 - 09:55

Thanks for correction.

NET_MHO_LAN_6880#telnet 10.40.11.30 25
Trying 10.40.11.30, 25 ... Open
220 MCEXCHHUB02.mclane.mclaneco.com Microsoft ESMTP MAIL Service ready at Fri, 15 Jul 2016 09:56:51 -0500


Helo NET_MHO_LAN_6880
250 MCEXCHHUB02.mclane.mclaneco.com Hello [10.40.11.7]

Joe Clarke Sun, 07/17/2016 - 04:09

This could be a socket bug.  From enable mode, run tclsh, then try:

set fd [socket 10.40.11.30 25]

info exists fd

close $fd

What do you get?

Eric Roach Mon, 07/18/2016 - 08:58

LAN_6880#tclsh


LAN_6880(tcl)#set fd [socket 10.40.11.30 25]
sock0
LAN_6880(tcl)#info exists fd
1
LAN_6880(tcl)#close $fd


LAN_6880(tcl)#tclquit

Joe Clarke Mon, 07/25/2016 - 12:30

Odd.  Okay, next steps would be to enable "debug event manager tcl mail" as well as get a sniffer trace of the tcp/25 traffic between the switch and the SMTP server when trying to execute your policy.

As you've been making changes to your Tcl policy, did you execute "no event manager policy ..." then "event manager policy ..." to re-register it?

Joe Clarke Sat, 10/03/2015 - 11:40

No, because for starters you don't use email_pre2 anywhere.  What you need to do is prepend the header to each MIME attachment.  So, you can do:

 

set mimeh "\n--EEM_email_boundary

Content-Type: application/octet-stream

Content-Transfer-Encoding: Base64"

 

Then append the final line for the file name each time you need it:

 

set mime1 $mimeh

append mime1 "Content-Disposition: attachment; filename=\"test.log\"\n\n"

set mime2 $mimeh

append mime2 "Content-Disposition: attachment; filename=\"test2.log\"\n\n"

 

Then construct your email using:

 

email_pre

mime1

email_b4

mime2

email_b64_2

email_body_mime

cpartsenidis Wed, 03/23/2016 - 19:31

Joe,

Thanks for providing the script.

I've been trying to use it without any success on a 2951 router.

The problem I'm having is that I am unable to execute or register the script.  I've saved the script as email.tcl and uploaded it to the router's flash.

When trying to register it using "event manager policy email.tcl type user"  I receive the following error:

" EEM Register event failed: Error empty reg spec, policy does not start with EEM registration commands.

EEM Configuration: failed to retrieve intermediate registration result for policy email.tcl"

My question is how can I use the script correctly ?

When I replaced the first line of the script with the following"

"::cisco::eem::event_register_syslog pattern $_syslog_pattern",  the script would register, but I couldn't trigger it even despite configuring the event manager environment _syslog_pattern

What I'm trying to do is use this script with an applet that is triggered when CPU usage is high - it creates a TXT file on the router's flash and I want it to be email to me.

Any help would be much appreciated!

Thank you.

cpartsenidis Wed, 03/23/2016 - 20:39

Just as a reference for other users, I had to change the following in the first line of the script:

::cisco::eem:event_register_none

Apparently the events have changed in the newer IOS version.

Also be very careful with the .TCL files - I had two identical .tcl files one would come up with the error : EEM Register event failed: Error empty reg spec, policy does not start with EEM registration commands:  while the other didn't.

The problem seems to be with my editor (Notepad ++ and Windows notepad), but I'm not sure why this is happening.

The file seems to need to be clear txt without any UTF or other encoding.

Many thanks,

Chris.

Firewall.cx

Actions

This Document

Related Content