Pragana's Tcl Guide

Old notes

simple mailer for many files

I got a problem: to send a very large file to a friend by e-mail. Of course, I have used "split" to break the file in many smaller ones, but how to deliver them? Not by hand, of course. I could have spent my time figuring another mailer (mine is pine) that could automate such thing, but hey! I'm a tcler, don't?
In just 15 minutes, I have devised a simple solution to send all files with a single command and perhaps this could be useful to you too. At least is a didatic approach to how a SMTP server works.

How does a SMTP server work ?

You can just do it by hand! By telnetting a server at the port 25, you're talking to its SMTP server, just like a dumb terminal would do. At the other side, instead of a shell with login, a demon, normally sendmail. It's protocol is very simple, with several 4-letter commands like RCPT, MAIL, DATA, and QUIT. Other commands exists like HELO, VRFY, &c, but we really don't need them.
The server returns a single line beginning with a number, the status code, followed by a short expanation of what happened. In this tiny implementation, we will not check the returned codes, but we'll echo them at our screen so, we can manually abort if something is wrong!

The implementation

To make things simpler, let's open a socket with line buffering, blocking, so not to catch file events at each answer received. This is done by the procedure connect:

proc connect { } {
	global chan host
	if {[catch {set chan [socket $host 25]} msg]} {
		puts -nonewline stderr "coudn't connect to "
		puts stderr "$host on port $port : $msg"
  		exit 1
	fconfigure $chan -blocking 1 -buffering line 
	puts [gets $chan]

The final puts is needed because we must wait for the server's answer.

To send a file (better, a single message), we first tell the server who we are, with the MAIL command, wait the answer, send the recipient's address with the RCPT command, and wait the answer. Then we're ready to send all mail's body. We start to send the data with the command DATA (what else?), and from now on, everything will be data lines, till a line with just a dot "." at it's first column.
We could put here a Subject: line, at the near beginning of the data. Then, we must encode the file to be sent, supposing it's a binary file. This is done by an external command, uuencode.

proc sendfile { fn } {
  	global chan from to
	puts "MAIL $from"
	puts $chan "MAIL $from"
	puts [gets $chan]
	puts "RCPT $to"
	puts $chan "RCPT $to"
	puts [gets $chan]
	puts $chan DATA
	puts [gets $chan]
	puts $chan "Subject: arquivo $fn"
	set data [exec uuencode $fn -]
	puts $chan $data
	puts $chan "."
	puts [gets $chan]

We echo the commands as they are sent (puts without $chan) to stdout, so we can figure what's going on.

wrapping up things

Now the main procedure is straightforward. Just connect, make a list of files to send and call the above procedure for each file to be sent. Please set the variables from, to, host and filepattern before trying!

# \
exec tclsh "$0" "$@"

set from "From:"
set to "To:"
set host ""
set filepattern AdmSist*

puts "connecting to server: $host"

foreach fn [glob $filepattern] {
	puts "sending $fn"
	sendfile $fn
puts $chan QUIT
close $chan
puts "finished!"

That's all fellows. Happy hacking!

Back Home