-
Notifications
You must be signed in to change notification settings - Fork 128
/
mailserver.d
189 lines (160 loc) · 4.24 KB
/
mailserver.d
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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
/++
A bare-bones, dead simple incoming SMTP server with zero outbound mail support. Intended for applications that want to process inbound email on a VM or something.
$(H2 Alternatives)
You can also run a real email server and process messages as they are delivered with a biff notification or get them from imap or something too.
History:
Written December 26, 2020, in a little over one hour. Don't expect much from it!
+/
module arsd.mailserver;
import arsd.fibersocket;
import arsd.email;
///
struct SmtpServerConfig {
//string iface = null;
ushort port = 25;
string hostname;
}
///
void serveSmtp(FiberManager fm, SmtpServerConfig config, void delegate(string[] recipients, IncomingEmailMessage) handler) {
fm.listenTcp4(config.port, (Socket socket) {
ubyte[512] buffer;
ubyte[] at;
const(ubyte)[] readLine() {
top:
int index = -1;
foreach(idx, b; at) {
if(b == 10) {
index = cast(int) idx;
break;
}
}
if(index != -1) {
auto got = at[0 .. index];
at = at[index + 1 .. $];
if(got.length) {
if(got[$-1] == '\n')
got = got[0 .. $-1];
if(got[$-1] == '\r')
got = got[0 .. $-1];
}
return got;
}
if(at.ptr is buffer.ptr && at.length < buffer.length) {
auto got = socket.receive(buffer[at.length .. $]);
if(got < 0) {
socket.close();
return null;
} if(got == 0) {
socket.close();
return null;
} else {
at = buffer[0 .. at.length + got];
goto top;
}
} else {
// no space
if(at.ptr is buffer.ptr)
at = at.dup;
auto got = socket.receive(buffer[]);
if(got <= 0) {
socket.close();
return null;
} else {
at ~= buffer[0 .. got];
goto top;
}
}
assert(0);
}
socket.sendAll("220 " ~ config.hostname ~ " SMTP arsd_mailserver\r\n"); // ESMTP?
immutable(ubyte)[][] msgLines;
string[] recipients;
loop: while(socket.isAlive()) {
auto line = readLine();
if(line is null) {
socket.close();
break;
}
if(line.length < 4) {
socket.sendAll("500 Unknown command");
continue;
}
switch(cast(string) line[0 .. 4]) {
case "HELO":
socket.sendAll("250 " ~ config.hostname ~ " Hello, good to see you\r\n");
break;
case "EHLO":
goto default; // FIXME
case "MAIL":
// MAIL FROM:<email address>
// 501 5.1.7 Syntax error in mailbox address "me@a?example.com.arsdnet.net" (non-printable character)
if(line.length < 11 || line[0 .. 10] != "MAIL FROM:") {
socket.sendAll("501 Syntax error");
continue;
}
line = line[10 .. $];
if(line[0] == '<') {
if(line[$-1] != '>') {
socket.sendAll("501 Syntax error");
continue;
}
line = line[1 .. $-1];
}
string currentDate; // FIXME
msgLines ~= cast(immutable(ubyte)[]) ("From " ~ cast(string) line ~ " " ~ currentDate);
msgLines ~= cast(immutable(ubyte)[]) ("Received: from " ~ socket.remoteAddress.toString);
socket.sendAll("250 OK\r\n");
break;
case "RCPT":
// RCPT TO:<...>
if(line.length < 9 || line[0 .. 8] != "RCPT TO:") {
socket.sendAll("501 Syntax error");
continue;
}
line = line[8 .. $];
if(line[0] == '<') {
if(line[$-1] != '>') {
socket.sendAll("501 Syntax error");
continue;
}
line = line[1 .. $-1];
}
recipients ~= (cast(char[]) line).idup;
socket.sendAll("250 OK\r\n");
break;
case "DATA":
socket.sendAll("354 Enter mail, end with . on line by itself\r\n");
more_lines:
line = readLine();
if(line == ".") {
handler(recipients, new IncomingEmailMessage(msgLines));
socket.sendAll("250 OK\r\n");
} else if(line is null) {
socket.close();
break loop;
} else {
msgLines ~= line.idup;
goto more_lines;
}
break;
case "QUIT":
socket.sendAll("221 Bye\r\n");
socket.close();
break;
default:
socket.sendAll("500 5.5.1 Command unrecognized\r\n");
}
}
});
}
version(Demo)
void main() {
auto fm = new FiberManager;
fm.serveSmtp(SmtpServerConfig(9025), (string[] recipients, IncomingEmailMessage iem) {
import std.stdio;
writeln(recipients);
writeln(iem.subject);
writeln(iem.textMessageBody);
});
fm.run;
}