1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
|
/** -*-C-*-ish
Kaya standard library
Copyright (C) 2004, 2005 Edwin Brady
This file is distributed under the terms of the GNU Lesser General
Public Licence. See COPYING for licence.
*/
"<summary>Sending mail via SMTP</summary>
<prose>This module allows the sending of electronic mail to an SMTP server.</prose>"
module Mail;
// Module for talking to an SMTP server
// Maybe add IMAP/POP3 support later?
import Prelude;
import Net;
import Strings;
import Regex;
import Time;
"SMTP Connection handle."
data SMTPConnection = SMTP(NetHandle h, String host);
"<summary>Could not connect to SMTP server</summary>
<prose>This Exception is thrown if the connection to the SMTP server failed.</prose>"
Exception CantConnect();
"<argument>Error description</argument>
<summary>Error talking to SMTP server</summary>
<prose>This Exception is thrown if the SMTP server returns an error.</prose>"
Exception SMTPError(String err);
"<argument>Error description</argument>
<summary>Badly-formed SMTP header</summary>
<prose>This Exception is thrown if the mail headers are not correctly formed (for example, if they contain a newline).</prose>"
Exception HeaderError(String err);
"<argument name='from'>The email address to send the message from</argument>
<argument name='whoto'>The email address to send the message to</argument>
<argument name='headers'>A list of name-value pairs for additional mail headers to send: consult RFC 2822 for further details.</argument>
<argument name='body'>The body of the message.</argument>
<argument name='server'>The hostname or IP address of the SMTP server (this is optional, and <code>localhost</code> will be used if it is not specified).</argument>
<argument name='port'>The TCP port on the server to connect to (this is optional and the standard SMTP port of 25 will be used if it is not specified).</argument>
<summary>Send a mail via SMTP</summary>
<prose>Sends an email via SMTP as specified in the email.</prose>
<example>body = \"...\"; // text of message goes here
headers = [(\"X-Mailer\",\"Kaya\"),
(\"Subject\",\"Example Message\"),
(\"Cc\",\"example2@kayalang.org\")];
sendmail(\"kaya@kayalang.org\",\"example1@kayalang.org\",headers,body);</example>
<prose>The <code>To</code>, <code>From</code> and <code>Date</code> headers are set automatically and do not need adding to the headers array. Only the body parameter may contain newlines.</prose>
<prose>An Exception will be thrown if the header values are invalid, the SMTP server is unavailable, or the SMTP server rejects the message.</prose>"
public Void sendmail(String from, String whoto, [(String,String)] headers,
String body, String server="localhost", Int port=25)
{
// check data and construct message
blacklist = compile("[\r\n]",[IgnoreCase,Multiline]);
if (quickMatch(blacklist,from)) {
throw(HeaderError("From is "+from));
}
if (quickMatch(blacklist,whoto)) {
throw(HeaderError("To is "+whoto));
}
msg = "";
for header in headers {
if (quickMatch(blacklist,header.fst) || quickMatch(blacklist,header.snd)) {
throw(HeaderError(header.fst+" is "+header.snd));
}
msg += header.fst+": "+header.snd+"\n";
}
msg += "From: "+from+"\n";
msg += "To: "+whoto+"\n";
msg += "Date: "+rfc2822Time(gmTime())+"\n\n"; // end headers
msg += body;
c = smtpConnect(server,port);
smtpFrom(c,from);
smtpRcpt(c,whoto);
smtpData(c,msg);
smtpClose(c);
}
// CIM: removed public status - the data type it returned was private, so
// no-one could actually have used it as public anyway!
"Create an SMTP connection"
SMTPConnection smtpConnect(String host, Int port=25)
{
h = connect(TCP,host,port);
rcvok = recv(h,255,30);
if ((words(rcvok)[0])!="220") {
throw(CantConnect);
}
send(h,"HELO "+host+"\n");
rcvok = recv(h,255,30);
if ((words(rcvok)[0])!="250") {
throw(CantConnect);
}
return SMTP(h,host);
}
Void smtpFrom(SMTPConnection c, String from)
{
send(c.h,"MAIL FROM:"+from+"\n");
rcvok = recv(c.h,255,30);
if ((words(rcvok)[0])!="250") {
throw(SMTPError(rcvok));
}
}
Void smtpRcpt(SMTPConnection c, String whoto)
{
addresses = split(",",whoto);
for address in addresses {
trim(address);
send(c.h,"RCPT TO:"+address+"\n");
rcvok = recv(c.h,255,30);
if ((words(rcvok)[0])!="250") {
throw(SMTPError(rcvok));
}
}
}
Void manglePeriod(var String stuff)
{
slines = lines(stuff);
newstuff = "";
for x in slines {
if (length(x) > 0 && head(x)=='.') {
x = "."+x;
}
newstuff += x +"\n";
}
stuff = newstuff;
}
// Send data. Returns message id.
Void smtpData(SMTPConnection c, String stuff)
{
send(c.h,"DATA\n");
rcvok = recv(c.h,255,30);
if ((words(rcvok)[0])!="354") {
throw(SMTPError(rcvok));
}
// Mangle stuff in case any lines start with a .
manglePeriod(stuff);
send(c.h,stuff+"\n");
send(c.h,".\n");
rcvok = recv(c.h,255,30);
if ((words(rcvok)[0])!="250") {
throw(SMTPError(rcvok));
}
}
Void smtpClose(SMTPConnection c)
{
send(c.h,"QUIT\n");
rcvok = recv(c.h,255,30);
if ((words(rcvok)[0])!="221") {
throw(SMTPError(rcvok));
}
closeConnection(c.h);
}
|