2 MegaBrutal's SMTP Server (MgSMTP)
3 Copyright (C) 2010-2018 MegaBrutal
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Affero General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Affero General Public License for more details.
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
21 This unit is responsible for listening for incoming connections, and
22 serve them, communicating by the SMTP protocol.
24 It always places incoming e-mails in the spool, and lets it to process
25 them later. However, this unit still links the Mailbox and Relay unit to
26 verify addresses. The Policies unit also plays an important role, it
27 determines what rights does the client have, and it authenticates users.
35 uses SysUtils
, Classes
, Base64
, Network
, NetRFC
, RFCSMTP
,
36 Common
, Log
, Policies
, Spool
, Mailbox
, Relay
;
40 TMgSMTPListener
= class(TTCPListener
)
41 constructor Create(const Address
: string; Port
: word);
43 procedure HandleClient(Connection
: TTCPConnection
); override;
44 procedure ReceiveEMailData(TCP
: TTCPRFCConnection
; Response
: TRFCReply
; SpoolObject
: TSpoolObjectCreator
);
46 function StartListen
: boolean;
50 procedure StartListeners
;
51 procedure StopListeners
;
58 MgSMTPListeners
: array of TMgSMTPListener
;
61 procedure StartListeners
;
62 var i
: integer; address
, port
: string;
64 SetLength(MgSMTPListeners
, MainServerConfig
.ListenAddresses
.Count
);
65 for i
:= 0 to Length(MgSMTPListeners
) - 1 do begin
66 SplitParameters(MainServerConfig
.ListenAddresses
.Strings
[i
], address
, port
, ':');
67 MgSMTPListeners
[i
]:= TMgSMTPListener
.Create(address
, StrToIntDef(port
, STANDARD_SMTP_PORT
));
68 MgSMTPListeners
[i
].StartListen
;
72 procedure StopListeners
;
75 for i
:= 0 to Length(MgSMTPListeners
) - 1 do begin
76 MgSMTPListeners
[i
].StopListen
;
77 MgSMTPListeners
[i
].Free
;
79 SetLength(MgSMTPListeners
, 0);
83 function Base64Decode(Source
: string): string;
84 var StringStream
: TStringStream
; Base64DecodingStream
: TBase64DecodingStream
;
87 StringStream
:= TStringStream
.Create(Source
);
88 Base64DecodingStream
:= TBase64DecodingStream
.Create(StringStream
);
90 while not Base64DecodingStream
.EOF
do begin
91 Base64DecodingStream
.Read(c
, 1);
94 Base64DecodingStream
.Destroy
;
98 procedure SetEMailProperties(Parameters
: string; SpoolObject
: TSpoolObject
);
99 var CPrm
, Rem
, Key
, Value
: string;
101 { Cut down e-mail address. }
102 SplitParameters(Parameters
, CPrm
, Rem
);
104 SplitParameters(Rem
, CPrm
, Rem
);
105 SplitParameters(CPrm
, Key
, Value
, '=');
106 Key
:= UpperCase(Key
);
107 if Key
= 'SIZE' then SpoolObject
.EMailProperties
.Size
:= StrToIntDef(Value
, 0)
108 else if Key
= 'BODY' then begin
109 if UpperCase(Value
) = '8BITMIME' then
110 SpoolObject
.EMailProperties
.SetFlag(EF_8BITMIME
);
115 function HandleRewrite(OriginalAddress
: string; Mailbox
: PMailbox
; SpoolObject
: TSpoolObjectCreator
): string;
118 for i
:= 0 to Mailbox
^.RewriteCount
- 1 do
119 SpoolObject
.Envelope
.AddRecipient(Mailbox
^.GetRewriteToEntry(i
));
120 if Mailbox
^.RewritePassThru
then
121 SpoolObject
.Envelope
.AddRecipient(OriginalAddress
);
122 if Mailbox
^.RewriteCount
> 0 then begin
123 if Mailbox
^.RewritePassThru
then
124 Result
:= 'Rewrite: ' + OriginalAddress
+ ' -> ' + OriginalAddress
+ ',' + Mailbox
^.GetRewriteToListStr
126 Result
:= 'Rewrite: ' + OriginalAddress
+ ' -> ' + Mailbox
^.GetRewriteToListStr
;
133 constructor TMgSMTPListener
.Create(const Address
: string; Port
: word);
135 { Request connection objects with support for RFC-style commands & responses. }
136 inherited Create(Address
, Port
, NET_TCP_RFCSUPPORT
);
140 function TMgSMTPListener
.StartListen
: boolean;
142 Result
:= inherited StartListen
;
144 Logger
.AddLine('Server', 'Listening on address: ' + GetSockAddrStr
)
146 Logger
.AddLine('Server', 'Failed to listen on address: ' + GetSockAddrStr
);
149 procedure TMgSMTPListener
.HandleClient(Connection
: TTCPConnection
);
150 { This is the procedure that actually handles the clients. It receives
151 an object that manages the established connection in the parameter.
152 TTCPConnection is defined in the Network unit. }
154 TCP
: TTCPRFCConnection
;
155 Originator
: TIPNamePair
;
157 PolicyObject
: TPolicyObject
;
158 SpoolObject
: TSpoolObjectCreator
;
159 Cmd
: shortstring
; Prm
, OPrm
: string;
160 Auth_Username
, Auth_Password
: string; FailedAuthAttempts
: integer;
161 HELOSent
, SpoolAllocated
, ReadSucceeded
, UnexpectedFail
: boolean;
162 VStr
: string; LogAgent
: string;
165 procedure SendAndLogResponse(NumericCode
: word; ReplyText
: shortstring
; ExpectFail
: boolean = false);
167 if (Logger
.AddLine(LogAgent
, 'Response: ' + IntToStr(NumericCode
) + ' ' + ReplyText
)) or ExpectFail
then begin
168 Response
.SetReply(NumericCode
, ReplyText
);
169 TCP
.SendResponse(Response
);
172 SendAndLogResponse(SMTP_R_SERVICE_NA
, 'Internal error: could not write log', true);
173 Logger
.AddStdLine(LogAgent
, 'Log write failure. Terminating active connection.');
174 UnexpectedFail
:= true;
179 TCP
:= Connection
as TTCPRFCConnection
;
180 TCP
.SetSockTimeOut(DEF_SOCK_TIMEOUT
);
181 TCP
.ReverseDNSLookup
;
182 Originator
:= TCP
.HostIP
.Copy
;
183 Response
:= TRFCReply
.Create
;
184 {PolicyObject:= PolicyManager.MakePolicyObject(Originator.Copy);}
185 PolicyObject
:= PolicyManager
.MakePolicyObject(Originator
);
187 HELOSent
:= false; SpoolAllocated
:= false; UnexpectedFail
:= false;
188 FailedAuthAttempts
:= 0;
190 { Prepare for logging. To make this connection distinguishable, we add
191 the actual thread's ID to each log entry. }
192 LogAgent
:= 'Server ' + IntToStr(GetCurrentThreadId
);
193 Logger
.AddLine(LogAgent
, 'Client connected: ' + Originator
.Name
+ ' (' + Originator
.IP
+ ')');
194 Logger
.AddLine(LogAgent
, 'Assigned rights (for host): ' + PolicyObject
.RightsStr
);
196 { Verify FCrDNS if necessary. Note, maybe it would have been simpler to
197 check it around the TCP.ReverseDNSLookup call, and then only pass the
198 trusted result to PolicyManager.MakePolicyObject. The main idea why I
199 didn't implement it that way is that I'd like to see if the granted
200 rights actually change after the FCrDNS check. }
201 if PolicyManager
.FCrDNSPolicy
<> FCRDNS_NAIVE
then begin
202 if not TCP
.VerifyFCrDNS
then begin
203 PolicyManager
.RevalidatePolicyObject(PolicyObject
, Originator
, false, PolicyManager
.FCrDNSPolicy
= FCRDNS_MEAN
);
204 if PolicyManager
.FCrDNSPolicy
= FCRDNS_STRICT
then
205 PolicyObject
.Deny(RIGHT_CONNECT
);
206 Logger
.AddLine(LogAgent
, 'WARNING: "' + Originator
.Name
+ '" is not a forward-confirmed reverse hostname! Rights will be reassigned by IP only!');
207 Logger
.AddLine(LogAgent
, 'Assigned rights (for host): ' + PolicyObject
.RightsStr
);
211 if PolicyObject
.HasRight(RIGHT_CONNECT
) then begin
212 if not PolicyManager
.HideVersion
then VStr
:= ' ' + MainServerConfig
.VersionStr
else VStr
:= '';
213 Response
.SetReply(SMTP_R_READY
, MainServerConfig
.Name
+ ' SMTP server ready (MgSMTP' + VStr
+ ')');
214 TCP
.SendResponse(Response
);
217 ReadSucceeded
:= TCP
.ReadCommand(Cmd
, Prm
);
219 { Check if command only contains printable ASCII characters, not some binary garbage. }
220 if ReadSucceeded
then begin
221 if IsPrintableString(Cmd
) and IsPrintableString(Prm
) then begin
222 Logger
.AddLine(LogAgent
, 'Command: ' + Cmd
+ ' ' + Prm
);
223 Cmd
:= UpperCase(Cmd
);
226 SendAndLogResponse(SMTP_R_SERVICE_NA
, 'Non-printable characters are not allowed in SMTP commands! Stop abusing my service!');
227 UnexpectedFail
:= true;
231 if (Length(Cmd
) = 0) or (not ReadSucceeded
) or UnexpectedFail
then { Nothing. }
233 else if (Cmd
= 'GET') or (Cmd
= 'HEAD') or (Cmd
= 'PUT') or (Cmd
= 'POST')
234 or (Cmd
= 'DELETE') or (Cmd
= 'CONNECT') or (Cmd
= 'OPTIONS')
235 or (Cmd
= 'PATCH') or (Cmd
= 'TRACE') then begin
236 SendAndLogResponse(SMTP_R_SERVICE_NA
, 'Please learn to speak SMTP for I won''t speak HTTP. Stop abusing my service!');
237 UnexpectedFail
:= true;
240 else if (Cmd
= SMTP_C_HELO
) or (Cmd
= SMTP_C_EHLO
) then begin
241 Response
.SetReply(SMTP_R_OK
, MainServerConfig
.Name
);
242 if Cmd
= SMTP_C_EHLO
then begin
243 Response
.Add('SIZE ' + IntToStr(PolicyObject
.Databytes
));
244 {Response.Add('VRFY');}
245 Response
.Add('PIPELINING');
246 Response
.Add('8BITMIME');
247 if PolicyManager
.Users
then begin
248 Response
.Add('AUTH LOGIN');
249 Response
.Add('AUTH=LOGIN');
252 TCP
.SendResponse(Response
);
254 Originator
:= TIPNamePair
.Create(Prm
, TCP
.HostIP
.IP
);
256 Logger
.AddLine(LogAgent
, 'Client identified: ' + Originator
.Name
+ ' (' + Originator
.IP
+ ')');
259 else if Cmd
= SMTP_C_AUTH
then begin
260 if PolicyManager
.Users
then begin
261 { Only "AUTH LOGIN" is supported. }
262 SplitParameters(Prm
, Prm
, OPrm
);
263 if Prm
= 'LOGIN' then begin
264 if OPrm
= '' then begin
265 { Base64-encoded "Username:" }
266 Response
.SetReply(SMTP_R_AUTH_MESSAGE
, 'VXNlcm5hbWU6');
267 TCP
.SendResponse(Response
);
268 TCP
.ReadLn(Auth_Username
);
269 Auth_Username
:= Base64Decode(Auth_Username
);
272 Auth_Username
:= Base64Decode(OPrm
);
273 { Base64-encoded "Password:" }
274 Response
.SetReply(SMTP_R_AUTH_MESSAGE
, 'UGFzc3dvcmQ6');
275 TCP
.SendResponse(Response
);
276 TCP
.ReadLn(Auth_Password
);
278 if PolicyManager
.AuthenticateUser(Auth_Username
, Base64Decode(Auth_Password
), PolicyObject
) then begin
279 Response
.SetReply(SMTP_R_AUTH_SUCCESSFUL
, 'Authentication successful');
280 Logger
.AddLine(LogAgent
, 'Successfully authenticated as user: ' + Auth_Username
);
281 Logger
.AddLine(LogAgent
, 'Assigned rights (for user): ' + PolicyObject
.RightsStr
);
284 Inc(FailedAuthAttempts
);
285 Response
.SetReply(SMTP_R_AUTH_FAILED
, 'Authentication failed');
286 Logger
.AddLine(LogAgent
, 'AUTHENTICATION FAILED as user: ' + Auth_Username
);
288 TCP
.SendResponse(Response
);
289 if (PolicyManager
.MaxAuthAttempts
<> 0) and (PolicyManager
.MaxAuthAttempts
<= FailedAuthAttempts
) then begin
290 SendAndLogResponse(SMTP_R_SERVICE_NA
, 'Too many unsuccessful authentication attempts! Stop abusing my service!');
291 UnexpectedFail
:= true;
292 Logger
.AddLine(LogAgent
, 'MAXIMUM AUTHENTICATION ATTEMPTS REACHED - DISCONNECTING CLIENT!');
296 SendAndLogResponse(SMTP_R_PRM_NOT_IMPLEMENTED
, 'Authentication type not implemented');
299 SendAndLogResponse(SMTP_R_CMD_NOT_IMPLEMENTED
, 'User authentication is not enabled on this server.');
302 else if Cmd
= SMTP_C_RSET
then begin
303 { We must be careful to always free the spool object, if we
304 have allocated one, but we don't need it anymore. }
305 if SpoolAllocated
then begin
306 if SpoolObject
.Opened
then SpoolObject
.Discard
;
308 SpoolAllocated
:= false;
310 Response
.SetReply(SMTP_R_OK
, 'OK');
311 TCP
.SendResponse(Response
);
314 else if Cmd
= SMTP_C_NOOP
then begin
315 Response
.SetReply(SMTP_R_OK
, 'Not like I was doing anything...');
316 TCP
.SendResponse(Response
);
319 else if Cmd
= SMTP_C_QUIT
then begin
320 { No extra action is required here to close the connection.
321 The repeat-until loop will quit anyway, and the connection
322 will be closed afterwards. }
323 Response
.SetReply(SMTP_R_CLOSE
, 'Goodbye. :)');
324 TCP
.SendResponse(Response
);
327 else if (HELOSent
) or (not PolicyManager
.ReqHELO
) then begin
329 { Some commands are only accepted after the client has greeted
330 us with a HELO or EHLO command. }
332 if Cmd
= SMTP_C_MAIL
then begin
333 { A new spool object is allocated with the mail command. }
334 if not SpoolAllocated
then begin
336 Prm
:= CleanEMailAddress(Prm
);
337 if (Prm
= '') or (IsValidEMailAddress(Prm
)) then begin
338 SpoolObject
:= SpoolManager
.CreateSpoolObject(Originator
.Copy
);
339 SpoolObject
.Envelope
.ReturnPath
:= Prm
;
340 SpoolObject
.Databytes
:= PolicyObject
.Databytes
;
341 SetEMailProperties(OPrm
, SpoolObject
);
342 if (SpoolObject
.EMailProperties
.Size
<= SpoolObject
.Databytes
) then begin
343 Response
.SetReply(SMTP_R_OK
, 'OK');
344 TCP
.SendResponse(Response
);
345 SpoolAllocated
:= true;
346 Logger
.AddLine(LogAgent
, 'Return-Path accepted: <' + Prm
+ '>');
349 SendAndLogResponse(SMTP_R_STOR_EXCEEDED
, 'Declared message size exceeds the configured databytes limit');
354 SendAndLogResponse(SMTP_R_MB_SYNTAX_ERROR
, '<' + Prm
+ '>: Sender address rejected: Syntax error');
357 SendAndLogResponse(SMTP_R_BAD_SEQUENCE
, 'Return-Path is already specified, use RSET to discard it');
360 else if Cmd
= SMTP_C_RCPT
then begin
361 if SpoolAllocated
then begin
362 Prm
:= CleanEMailAddress(Prm
);
364 { According to the RFC, we must accept "POSTMASTER" address without a hostname. }
365 if UpperCase(Prm
) = 'POSTMASTER' then Prm
:= Prm
+ '@' + MainServerConfig
.Name
;
366 if IsValidEMailAddress(Prm
) then begin
368 if MailboxManager
.IsLocalAddress(Prm
) then begin
370 { Many conditions need to be checked before accepting a local e-mail:
371 - Does this server accept local e-mails by configuration?
372 - Does the client have the right to STORE a local e-mail?
373 - Does the addressed mailbox exist?
374 - Does the mailbox have free quota?
375 If the answer is "no" for any of these questions, reject the address
376 with a proper error response. }
378 if MainServerConfig
.Mailbox
then begin
379 if PolicyObject
.HasRight(RIGHT_STORE
) then begin
380 if MailboxManager
.Verify(Prm
) then begin
381 if MailboxManager
.VerifyAlias(Prm
) then begin
382 if ((not SpoolManager
.AllowExceedQuota
) and (MailboxManager
.CheckQuota(EMailUserName(Prm
), EMailHost(Prm
), SpoolObject
.EMailProperties
.Size
)))
383 or ((SpoolManager
.AllowExceedQuota
) and (MailboxManager
.CheckQuota(EMailUserName(Prm
), EMailHost(Prm
), 0))) then begin
385 if MailboxManager
.Rewrite
then begin
386 TempStr
:= HandleRewrite(Prm
, MailboxManager
.GetMailbox(EMailUserName(Prm
), EMailHost(Prm
)), SpoolObject
);
387 if Length(TempStr
) > 0 then
388 Logger
.AddLine(LogAgent
, TempStr
);
391 SpoolObject
.Envelope
.AddRecipient(Prm
);
393 Response
.SetReply(SMTP_R_OK
, 'OK');
394 TCP
.SendResponse(Response
);
395 Logger
.AddLine(LogAgent
, 'Local recipient accepted: <' + Prm
+ '>');
398 SendAndLogResponse(SMTP_R_STOR_EXCEEDED
, '<' + Prm
+ '>: User quota exceeded');
401 SendAndLogResponse(SMTP_R_MAILBOX_NA
, '<' + Prm
+ '>: Mailbox alias rejected');
404 SendAndLogResponse(SMTP_R_MAILBOX_NA
, '<' + Prm
+ '>: No mailbox here by that name');
407 SendAndLogResponse(SMTP_R_MAILBOX_NA
, '<' + Prm
+ '>: Store access denied');
410 SendAndLogResponse(SMTP_R_MAILBOX_NA
, '<' + Prm
+ '>: This server doesn''t store local messages');
413 else if MainServerConfig
.Relay
then begin
415 { Things to check for relay addresses:
416 - Does the server ever accept relay addresses by configuration?
417 - Does the client has the right to RELAY messages or in the case
418 if the relay address is on the RelayTo list, does the client
422 if (PolicyObject
.HasRight(RIGHT_RELAY
))
423 or (PolicyObject
.HasRight(RIGHT_STORE
) and RelayManager
.IsOnRelayToList(EMailHost(Prm
))) then begin
424 if not RelayManager
.IsOnNoRelayToList(EMailHost(Prm
)) then begin
425 SpoolObject
.Envelope
.AddRecipient(Prm
);
426 Response
.SetReply(SMTP_R_OK
, 'OK');
427 TCP
.SendResponse(Response
);
428 Logger
.AddLine(LogAgent
, 'Relay recipient accepted: <' + Prm
+ '>');
431 SendAndLogResponse(SMTP_R_TRANS_FAILED
, '<' + Prm
+ '>: Relaying towards this domain is not permitted');
434 SendAndLogResponse(SMTP_R_TRANS_FAILED
, '<' + Prm
+ '>: Relay access denied, or maybe I just don''t like you');
437 SendAndLogResponse(SMTP_R_TRANS_FAILED
, '<' + Prm
+ '>: Relaying has been disabled by configuration');
440 SendAndLogResponse(SMTP_R_MB_SYNTAX_ERROR
, '<' + Prm
+ '>: Recipient address rejected: Syntax error');
443 SendAndLogResponse(SMTP_R_BAD_SEQUENCE
, 'You must initiate e-mail transactions with MAIL command');
446 else if Cmd
= SMTP_C_DATA
then begin
447 if SpoolAllocated
then begin
448 if SpoolObject
.Envelope
.IsComplete
then begin
449 ReceiveEMailData(TCP
, Response
, SpoolObject
);
450 Logger
.AddLine(LogAgent
, 'Response: ' + IntToStr(Response
.NumericCode
) + ' ' + Response
.GetLine(0));
451 TCP
.SendResponse(Response
);
452 Logger
.AddLine('Object ' + SpoolObject
.Name
, 'Message-ID: <' + SpoolObject
.OriginalMessageID
+ '>');
454 SpoolAllocated
:= false;
457 SendAndLogResponse(SMTP_R_TRANS_FAILED
, 'No valid recipients');
460 SendAndLogResponse(SMTP_R_BAD_SEQUENCE
, 'You must initiate e-mail transactions with MAIL command');
463 else if Cmd
= SMTP_C_VRFY
then
464 SendAndLogResponse(SMTP_R_CANNOTVERIFY
, 'Honestly, I don''t like to verify addresses')
467 SendAndLogResponse(SMTP_R_CMD_SYNTAX_ERROR
, 'Command not recognized (' + Cmd
+ ')');
471 SendAndLogResponse(SMTP_R_BAD_SEQUENCE
, 'It would be more polite to say HELO first');
473 until (Cmd
= SMTP_C_QUIT
) or (not ReadSucceeded
) or (UnexpectedFail
);
475 if not ReadSucceeded
then
476 SendAndLogResponse(SMTP_R_SERVICE_NA
, 'Socket read error');
481 { If the client doesn't have the right to CONNECT here, disconnect it
482 with a rather unfriendly message. }
484 SendAndLogResponse(SMTP_R_TRANS_FAILED
, 'Host is not permitted by server configuration');
485 SendAndLogResponse(SMTP_R_SERVICE_NA
, 'You are not welcome here, I shall disconnect you');
487 TCP.ReadCommand(Cmd, Prm);
488 if Cmd <> SMTP_C_QUIT then
489 Response.SetReply(SMTP_R_BAD_SEQUENCE, 'You are not welcome here, I suggest you to QUIT')
491 Response.SetReply(SMTP_R_CLOSE, 'Closing connection');
492 TCP.SendResponse(Response);
493 until Cmd = SMTP_C_QUIT;}
496 { Free the spool object (if we have any), close the connection,
497 and free other allocated resources, log disconnection. }
499 if SpoolAllocated
then begin
500 if SpoolObject
.Opened
then SpoolObject
.Discard
;
507 Logger
.AddLine(LogAgent
, 'Client disconnected.');
510 procedure TMgSMTPListener
.ReceiveEMailData(TCP
: TTCPRFCConnection
; Response
: TRFCReply
; SpoolObject
: TSpoolObjectCreator
);
511 { Receive e-mail lines until a line with a single dot (".") arrives.
512 Check databytes limit!
513 This procedure should never call TCP.SendResponse - the set up response
514 will be sent by the caller! }
515 var Line
: string; Done
, ReadOK
: boolean;
517 if SpoolObject
.Open
then begin
518 Response
.SetReply(SMTP_R_START_MAIL_INPUT
, 'Start mail input; end with "<CRLF>.<CRLF>" sequence');
519 TCP
.SendResponse(Response
);
522 ReadOK
:= TCP
.ReadLn(Line
);
523 if Line
<> '.' then begin
524 { If the line starts with a dot, remove it to comply with RFC. }
525 if (Length(Line
) > 1) and (Line
[1] = '.') then Delete(Line
, 1, 1);
526 SpoolObject
.DeliverMessagePart(Line
);
530 until Done
or (not ReadOK
);
532 if SpoolObject
.GetErrorCode
<> SCE_NO_ERROR
then begin
534 case SpoolObject
.GetErrorCode
of
537 Response
.SetReply(SMTP_R_STOR_EXCEEDED
, 'Message size exceeds the configured databytes limit');
542 Response
.SetNumericCode(SMTP_R_TRANS_FAILED
);
543 Response
.Add('Too many "Received" headers in mail data.');
544 Response
.Add('It''s likely that your message got trapped in a mail relay loop. In most');
545 Response
.Add('cases it is caused by faulty mail server configuration. Please notify the');
546 Response
.Add('administrator by forwarding this failure notice to the following address:');
547 Response
.Add('<postmaster@' + MainServerConfig
.Name
+ '>!');
551 Response
.SetReply(SMTP_R_ABORTED
, 'Could not write mail data. Try again later.');
554 Response
.SetReply(SMTP_R_ABORTED
, 'Unknown error. Could not queue mail data.');
562 Response
.SetReply(SMTP_R_OK
, 'Queued as ' + SpoolObject
.Name
);
567 Response
.SetReply(SMTP_R_SERVICE_NA
, 'Socket read error in DATA phase (timeout?)');
571 else Response
.SetReply(SMTP_R_ABORTED
, 'Internal error: could not open spool object');