File: Mail.k

package info (click to toggle)
kaya 0.4.2-4
  • links: PTS
  • area: main
  • in suites: lenny
  • size: 4,448 kB
  • ctags: 1,694
  • sloc: cpp: 9,536; haskell: 7,461; sh: 3,013; yacc: 910; makefile: 816; perl: 90
file content (163 lines) | stat: -rw-r--r-- 5,282 bytes parent folder | download | duplicates (4)
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);
}