1# send_mail.bash
2# Author: Noah Friedman <friedman@prep.ai.mit.edu>
3# Created: 1992-07-02
4# Public domain
5
6# Commentary:
7
8# TODO: implement Fcc headers (see emacs manual)
9
10# Code:
11
12#:docstring send_mail:
13# Usage: send_mail
14#
15# This function serves as a simple replacement for sendmail as a client
16# interface on those systems where it is not available.  It does assume
17# that one can talk to an SMTP mailer on port 25 either on the local host
18# or on the host specified by the MAILHOST environment variable.  If you
19# have access to sendmail, it's better to use 'sendmail -t' instead of this
20# script (which probably isn't as robust).
21#
22# Message is read from stdin, and headers are parsed to determine
23# recipients.  
24#:end docstring:
25
26###;;;autoload
27function send_mail ()
28{
29    # Need gawk, since several extensions are taken advantage of (like
30    # IGNORECASE for regexps).
31    local awk="${GAWK_LOCATION:-gawk}"
32    local DefaultFrom="${USER:-${LOGNAME}}"
33    local From
34    local To
35    local Cc
36    local Bcc
37    local tmpfile="/tmp/send_mail$$"
38
39    while [ -e "${tmpfile}" ]; do
40       tmpfile="/tmp/send_mail${RANDOM}"
41    done
42
43    # Lines consisting only of dots need one more dot appended.  SMTP
44    # servers eat one of the dots (and if only 1 dot appears, it signifies
45    # the end of the message).
46    sed '/^\.\.*/s/^\(\.\.*\)$/\1./' > "${tmpfile}"
47
48    # Parse mail headers in message to extract recipients list. 
49    # This doesn't affect what the user sees---it's only used to generate
50    # the rcpt-to lines for SMTP. 
51    eval $(${awk} -f - "${tmpfile}" <<- '__EOF__'
52       # Try to extract email address from amidst random data
53       function parse_address (data)
54       {
55           # From: "real name" <foobar@host>
56           # From: "" <foobar@host>
57           if (match(data, /^\"[^\"]*\"[ \t]*<.*>/)) {
58              data_idx = match(data, /^\"[^\"]*\"[ \t]*</)
59              data = substr(data, RSTART + RLENGTH);
60              if (data_idx = match(data, ">.*"))
61                 data = substr(data, 1, RSTART - 1);
62              return data
63           }
64           # From: real name <foobar@host>
65           if (match(data, /<.*>/)) {
66              data_idx = match(data, /</)
67              data = substr(data, RSTART + RLENGTH);
68              if (data_idx = match(data, ">"))
69                 data = substr(data, 1, RSTART - 1);
70              return data
71           }
72           # From: foobar@host (real name)
73           if (match(data, /\(.*\)/)) {
74              data_idx = match(data, /\(/);
75              data = substr(data, 1, RSTART - 1);
76              return data
77           }
78           # (hopefully) From: foobar@host
79           return data
80       }
81
82       BEGIN { IGNORECASE = 1; }
83
84       # Blank line signifies end of headers, so we can stop looking.
85       /^$/ { exit(0) }
86
87       /^from:|^to:|^cc:|^bcc:/ {
88          header_idx = match($0, /^[^:]*:/)
89          if (header_idx) {
90             # Capitalize header name
91             header_firstchar = toupper(substr($0, RSTART, 1));
92             header_rest = tolower(substr($0, RSTART + 1, RLENGTH - 2));
93             header = header_firstchar header_rest
94
95             $0 = substr($0, RSTART + RLENGTH + 1);
96             addresses = ""
97             # parse addresses
98             while ($0) {
99                # Strip leading whitespace
100                if (idx = match($0, /[ \t]*/))
101                   $0 = substr($0, RSTART + RLENGTH);
102
103                # Find everything up to a nonquoted comma
104                # FIXME: doesnt handle quoting yet
105                if (idx = match($0, /,/)) {
106                   data = substr($0, 1, RSTART);
107                   $0 = substr($0, RSTART + 1);
108                } else {
109                   data = $0
110                   $0 = ""
111                }
112                addresses = addresses " " parse_address(data)
113             }
114
115             printf("%s='%s'\n", header, addresses);
116          }
117       }
118	__EOF__)
119
120    # Not sure if an address is *required* after the HELO.. every sendmail
121    # I tried talking to didn't seem to care.  Some sendmails don't care
122    # if there's a HELO at all. 
123    cat <<- __EOF__ | telnet ${MAILHOST:-localhost} 25 > /dev/null 2>&1
124	HELO
125	mail from: ${From:-${DefaultFrom}}
126	$(for name in ${To} ${Cc} ${Bcc} ; do
127	     echo "rcpt to: ${name}"
128	  done)
129	data
130	$(cat "${tmpfile}")
131	.
132	quit
133	__EOF__
134
135    rm -f "${tmpfile}"
136}
137
138provide send_mail
139
140# send_mail.bash ends here
141