Bind to user-specified address (BindAddress6)
[mgsmtp.git] / Common.pas
1 {
2 Copyright (C) 2010-2018 MegaBrutal
3
4 This unit is free software: you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
8
9 This unit is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
16 }
17
18 {
19 Unit: Common
20 It holds some common definitions for MgSMTP, and some helper functions.
21 }
22
23
24 {$MODE DELPHI}
25
26 unit Common;
27
28 interface
29 uses Windows, SysUtils, DateUtils, Classes, INIFiles, RFCSMTP;
30
31 type
32
33 TStringArray = array of string;
34 TUnixTimeStamp = longint;
35
36
37 TArgument = record
38 Option, Value: string;
39 end;
40
41
42 TArgumentParser = class
43 constructor Create(RawArguments: array of string; AllowedPrefixes: array of string);
44 destructor Destroy; override;
45 private
46 Arguments: array of TArgument;
47 procedure ParseArgument(Arg: string; const AllowedPrefixes: array of string);
48 public
49 function GetArgument(ID: integer): TArgument;
50 function IsPresent(ArgumentName: string): boolean;
51 function GetValue(ArgumentName: string; DefValue: string = ''): string;
52 function ValidateArguments(ValidArguments: array of string): integer;
53 end;
54
55
56 TNamedObject = class
57 constructor Create(const Name: string; Config: TINIFile; const Section: string);
58 private
59 FName: string;
60 FAliases: TStrings;
61 public
62 property Name: string read FName;
63 property Aliases: TStrings read FAliases;
64 function IsItYourName(const Name: string): boolean; virtual;
65 end;
66
67
68 TMainServerConfig = class(TNamedObject)
69 constructor Create(Config: TINIFile);
70 private
71 FPolicies, FMailbox, FRelay, FLog: boolean;
72 FDatabytes: longint;
73 {FTimeCorrection: integer;}
74 FTimeOffset: integer;
75 FTimeOffsetStr: string;
76 FListenAddresses, FListenAddresses6: TStrings;
77 FBindAddress, FBindAddress6: string;
78 public
79 function GetVersionStr: string;
80 property ListenAddresses: TStrings read FListenAddresses;
81 property ListenAddresses6: TStrings read FListenAddresses6;
82 property BindAddress: string read FBindAddress;
83 property BindAddress6: string read FBindAddress6;
84 property Databytes: longint read FDatabytes;
85 {property TimeCorrection: integer read FTimeCorrection;}
86 property TimeOffset: integer read FTimeOffset;
87 property TimeOffsetStr: string read FTimeOffsetStr;
88 property Policies: boolean read FPolicies;
89 property Mailbox: boolean read FMailbox;
90 property Relay: boolean read FRelay;
91 property Log: boolean read FLog;
92 property VersionStr: string read GetVersionStr;
93 end;
94
95
96 TEMailFlags = word;
97
98 TEMailProperties = class
99 constructor Create;
100 protected
101 FSize: longint;
102 FFlags: TEMailFlags;
103 public
104 procedure SetSize(Value: longint);
105 procedure WriteFlags(Value: TEMailFlags);
106 procedure SetFlag(Flag: TEMailFlags);
107 function HasFlag(Flag: TEMailFlags): boolean;
108 property Size: longint read FSize write SetSize;
109 property Flags: TEMailFlags read FFlags write WriteFlags;
110 end;
111
112
113 TRecipient = record
114 Address, RMsg: string;
115 Data: integer;
116 end;
117
118
119 TIPNamePair = class
120 constructor Create(const Name, IP: string);
121 protected
122 FName, FIP: string;
123 public
124 property Name: string read FName;
125 property IP: string read FIP;
126 function Copy: TIPNamePair;
127 end;
128
129
130 TEnvelope = class
131 constructor Create;
132 destructor Destroy; override;
133 private
134 FReturnPath, FRelayHost: string;
135 FReturnPathSpecified: boolean;
136 FRecipients: array of TRecipient;
137 public
138 function GetNumberOfRecipients: integer;
139 function GetRecipient(Index: integer): TRecipient;
140 function IsComplete: boolean;
141 procedure AddRecipient(Address: string; Data: integer = 0; RMsg: string = ''); overload;
142 procedure AddRecipient(Recipient: TRecipient); overload;
143 procedure SetReturnPath(Address: string);
144 procedure SetRecipientData(Index, Data: integer; RMsg: string = '');
145 procedure SetAllRecipientData(Data: integer; RMsg: string = '');
146 procedure SetRelayHost(HostName: string);
147 property ReturnPath: string read FReturnPath write SetReturnPath;
148 property RelayHost: string read FRelayHost write SetRelayHost;
149 end;
150
151
152 TEnvelopeArray = array of TEnvelope;
153
154
155 function EMailUserName(EMail: string): string;
156 function EMailHost(EMail: string): string;
157 function CleanEMailAddress(EMail: string): string;
158 function IsValidEMailAddress(EMail: string): boolean;
159 function EMailTimeStamp(DateTime: TDateTime): string;
160 function EMailTimeStampCorrected(DateTime: TDateTime): string;
161 function StatusToStr(Status: integer): string;
162 procedure AssignDeliveryStatusToSMTPCodes(Envelope: TEnvelope; TransactionComplete: boolean);
163
164 function CleanEOLN(S: string): string;
165 function GenerateRandomString(Length: integer): string;
166 function GetAlphabetStr: string;
167 function GetServiceCodeStr(Ctrl: dword): string;
168 function GetWinMajorVersion: longword;
169 function IsPrintableString(S: string): boolean;
170 function UnixTimeStamp(DateTime: TDateTime): TUnixTimeStamp;
171 function CmdlineToStringArray: TStringArray;
172 procedure ParseIPv6Address(S: string; var Address: string; var Port: word);
173 procedure SplitParameters(S: string; var FirstPrm, Remainder: string; Separator: char = #32);
174
175 function ReadLineFromStream(Stream: TStream): string;
176 function WriteLineToStream(Stream: TStream; Line: string): boolean;
177
178
179 const
180
181 { MgSMTP version: }
182 VERSION_STR = '0.9t';
183
184 { Architecture: }
185 {$IFDEF CPU64}
186 PLATFORM_BITS = 64;
187 {$ELSE}
188 {$IFDEF CPU32}
189 PLATFORM_BITS = 32;
190 {$ENDIF}
191 {$ENDIF}
192
193 { Delivery statuses: }
194 DS_DELIVERED = 1 shl 10;
195 DS_DELAYED = 1 shl 11;
196 DS_PERMANENT = 1 shl 12;
197 DS_INTERNALFAIL = 1 shl 13;
198 DS_CONNECTIONFAIL = 1 shl 14;
199 DS_UNEXPECTEDFAIL = 1 shl 15;
200 DS_SMTPFAIL = 1 shl 16;
201 DS_SMTPREPLYMASK = $000003FF;
202 DS_ALLFLAGS = $FFFFFFFF;
203
204 { E-mail property flags: }
205 EF_8BITMIME = 1;
206
207 DayNames: array[1..7] of shortstring = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat');
208 MonthNames: array[1..12] of shortstring = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
209 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');
210
211 { Support for PRESHUTDOWN is not yet present in the Free Pascal library,
212 therefore I define the necessary constants here. It's a temporary
213 solution, I hope I won't need it for the next release of FPC. }
214 SERVICE_ACCEPT_PRESHUTDOWN = $00000100;
215 SERVICE_CONTROL_PRESHUTDOWN = $0000000F;
216
217
218 var
219
220 MainServerConfig: TMainServerConfig;
221
222
223 implementation
224
225
226 { Unit-private functions/prodecures: }
227
228 function InStringArray(const S: string; const SA: array of string): boolean;
229 var i: integer;
230 begin
231 i:= 0;
232 while (i < Length(SA)) and (SA[i] <> S) do Inc(i);
233 Result:= i < Length(SA);
234 end;
235
236 function MakeTimeOffsetStr(TimeOffset: integer): string;
237 var CorrS: string; CorrI: integer;
238 begin
239 CorrI:= TimeOffset;
240 CorrS:= IntToStr(Abs(CorrI));
241 while Length(CorrS) < 4 do CorrS:= '0' + CorrS;
242 if CorrI >= 0 then CorrS:= '+' + CorrS else CorrS:= '-' + CorrS;
243 Result:= CorrS;
244 end;
245
246
247 { Unit-public functions/procedures: }
248
249 function EMailUserName(EMail: string): string;
250 var p: integer;
251 begin
252 p:= Length(EMail);
253 while (p > 0) and (EMail[p] <> '@') do Dec(p);
254 if p <> 0 then begin
255 Result:= Copy(EMail, 1, p - 1);
256 end
257 else Result:= EMail;
258 end;
259
260 function EMailHost(EMail: string): string;
261 var p: integer;
262 begin
263 p:= Length(EMail);
264 while (p > 0) and (EMail[p] <> '@') do Dec(p);
265 if (p <> 0) and (p < Length(EMail)) then begin
266 Result:= Copy(EMail, p + 1, Length(EMail) - p);
267 end
268 else Result:= '';
269 end;
270
271 function CleanEMailAddress(EMail: string): string;
272 var po, pc, p: integer;
273 begin
274 po:= Pos('<', EMail);
275 pc:= Pos('>', EMail);
276 if (po <> 0) and (pc <> 0) and (po < pc) then begin
277 Result:= Copy(EMail, po + 1, pc - po - 1);
278 p:= Pos(':', Result);
279 if p <> 0 then
280 Result:= Copy(Result, p + 1, Length(Result) - p);
281 end
282 else
283 Result:= EMail;
284 end;
285
286 function IsValidEMailAddress(EMail: string): boolean;
287 begin
288 { !!! TODO: Implement more strict checking later !!! }
289 Result:= Pos('@', EMail) <> 0;
290 end;
291
292 function EMailTimeStamp(DateTime: TDateTime): string;
293 var Year, Month, Day: word;
294 begin
295 DecodeDate(DateTime, Year, Month, Day);
296 Result:= DayNames[DayOfWeek(DateTime)] + ' ' + MonthNames[Month] + ' '
297 + FormatDateTime('dd hh:nn:ss', DateTime) + ' ' + IntToStr(Year);
298 end;
299
300 function EMailTimeStampCorrected(DateTime: TDateTime): string;
301 begin
302 Result:= EMailTimeStamp(DateTime) + ' ' + MainServerConfig.TimeOffsetStr;
303 end;
304
305 function StatusToStr(Status: integer): string;
306 { Returns the delivery status code in human-readable format. }
307 begin
308 Result:= IntToStr(Status and (DS_ALLFLAGS - DS_SMTPREPLYMASK - DS_SMTPFAIL))
309 + '+' + IntToStr(Status and DS_SMTPREPLYMASK);
310 end;
311
312 procedure AssignDeliveryStatusToSMTPCodes(Envelope: TEnvelope; TransactionComplete: boolean);
313 var i, code, cond, status: integer; Recipient: TRecipient;
314 begin
315 for i:= 0 to Envelope.GetNumberOfRecipients - 1 do begin
316 Recipient:= Envelope.GetRecipient(i);
317 code:= Recipient.Data and DS_SMTPREPLYMASK;
318 cond:= code div 100;
319 case cond of
320 0: status:= DS_DELAYED or DS_UNEXPECTEDFAIL;
321 2: if TransactionComplete then status:= DS_DELIVERED
322 else status:= DS_DELAYED or DS_UNEXPECTEDFAIL;
323 4: status:= DS_DELAYED;
324 5: status:= DS_PERMANENT;
325 else status:= DS_PERMANENT or DS_UNEXPECTEDFAIL;
326 end;
327 if code <> 0 then status:= status or DS_SMTPFAIL;
328 Envelope.SetRecipientData(i, code or status, Recipient.RMsg);
329 end;
330 end;
331
332
333 function CleanEOLN(S: string): string;
334 begin
335 while (Length(S) <> 0) and (S[Length(S)] in [#13, #10]) do Delete(S, Length(S), 1);
336 Result:= S;
337 end;
338
339 function GenerateRandomString(Length: integer): string;
340 var What, Chrn, i: integer; Value: string;
341 begin
342 Value:= '';
343 for i:= 1 to Length do begin
344 What:= Random(3);
345 case What of
346 0: begin Chrn:= Random(10)+48; Value:= Value + Chr(Chrn); end;
347 1: begin Chrn:= Random(26)+65; Value:= Value + Chr(Chrn); end;
348 2: begin Chrn:= Random(26)+97; Value:= Value + Chr(Chrn); end;
349 end;
350 end;
351 Result:= Value;
352 end;
353
354 function GetAlphabetStr: string;
355 var i: byte;
356 begin
357 Result:= '';
358 for i:= Ord('0') to Ord('9') do Result:= Result + Chr(i);
359 for i:= Ord('A') to Ord('Z') do Result:= Result + Chr(i);
360 end;
361
362 function GetServiceCodeStr(Ctrl: dword): string;
363 begin
364 case Ctrl of
365 SERVICE_CONTROL_STOP: Result:= 'STOP';
366 SERVICE_CONTROL_SHUTDOWN: Result:= 'SHUTDOWN';
367 SERVICE_CONTROL_PRESHUTDOWN: Result:= 'PRESHUTDOWN';
368 else Result:= IntToStr(Ctrl);
369 end;
370 end;
371
372 function GetWinMajorVersion: longword;
373 var OSVersionInfo: TOSVersionInfo;
374 begin
375 { Get OS version info. }
376 OSVersionInfo.dwOSVersionInfoSize:= SizeOf(TOSVersionInfo);
377 GetVersionEx(OSVersionInfo);
378 Result:= OSVersionInfo.dwMajorVersion;
379 end;
380
381 function IsPrintableString(S: string): boolean;
382 { Check if string contains only printable ASCII characters. }
383 var i: integer;
384 begin
385 i:= 1;
386 Result:= true;
387 while Result and (i <= Length(S)) do begin
388 Result:= (Ord(S[i]) > 31) and (Ord(S[i]) < 127);
389 Inc(i);
390 end;
391 end;
392
393 procedure ParseIPv6Address(S: string; var Address: string; var Port: word);
394 { IPv6 addresses can be supplied in the following formats:
395 [<IPv6 address>]:<port> e.g. [::1]:25
396 or
397 <hostname>:<port> e.g. mail.example.com:25 }
398 var SPort: string; c: integer;
399 begin
400 if S[1] = '[' then begin
401 { Guess format is "[<IPv6 address>]:<port>". }
402 c:= pos(']', S);
403 if c > 1 then begin
404 Address:= Copy(S, 2, c - 2);
405 if c = Length(S) then begin
406 { There is no port to extract. }
407 Port:= STANDARD_SMTP_PORT;
408 end
409 else begin
410 { The closing bracket should be followed by a colon. }
411 if S[c+1] = ':' then
412 { Extract port number. }
413 Port:= StrToIntDef(Copy(S, c+2, Length(S) - (c+1)), 0)
414 else
415 { Invalid format. }
416 Port:= 0;
417 end;
418 end
419 else begin
420 { Format is incorrect, return invalid data. }
421 Address:= '';
422 Port:= 0;
423 end;
424 end
425 else begin
426 { Guess format is "<hostname>:<port>". }
427 SplitParameters(S, Address, SPort, ':');
428 if (pos(':', Address) = 0) and (pos(':', SPort) = 0) then begin
429 { Format seems correct. }
430 if SPort = '' then Port:= STANDARD_SMTP_PORT
431 else Port:= StrToIntDef(SPort, 0);
432 end
433 else begin
434 { Format is incorrect, return invalid data. }
435 Address:= '';
436 Port:= 0;
437 end;
438 end;
439 end;
440
441 procedure SplitParameters(S: string; var FirstPrm, Remainder: string; Separator: char = #32);
442 var i: integer;
443 begin
444 i:= pos(Separator, S);
445 if i > 0 then begin
446 FirstPrm:= Copy(S, 1, i - 1);
447 Remainder:= Copy(S, i + 1, Length(S) - i);
448 end
449 else begin
450 FirstPrm:= S;
451 Remainder:= '';
452 end;
453 end;
454
455 function CmdlineToStringArray: TStringArray;
456 var i: integer;
457 begin
458 SetLength(Result, ParamCount);
459 for i:= 1 to ParamCount do
460 Result[i-1]:= ParamStr(i);
461 end;
462
463 function UnixTimeStamp(DateTime: TDateTime): TUnixTimeStamp;
464 begin
465 {Result:= Trunc((DateTime - EncodeDate(1970, 1 ,1)) * 24 * 60 * 60);}
466 Result:= DateTimeToUnix(DateTime);
467 end;
468
469
470 function ReadLineFromStream(Stream: TStream): string;
471 var S: string; B: char;
472 begin
473 S:= '';
474 try
475 repeat
476 B:= Char(Stream.ReadByte);
477 if not (B in [#10, #13]) then S:= S + B;
478 until (B = #10);
479 finally
480 Result:= S;
481 end;
482 end;
483
484 function WriteLineToStream(Stream: TStream; Line: string): boolean;
485 const EOLN = #13#10;
486 begin
487 Result:= true;
488 Line:= Line + EOLN;
489 try
490 Stream.WriteBuffer(PChar(Line)^, Length(Line));
491 except
492 Result:= false;
493 end;
494 end;
495
496
497 { Object constructors/destructors: }
498
499 constructor TArgumentParser.Create(RawArguments: array of string; AllowedPrefixes: array of string);
500 var i: integer;
501 begin
502 for i:= 0 to Length(RawArguments) - 1 do
503 ParseArgument(RawArguments[i], AllowedPrefixes);
504 end;
505
506 destructor TArgumentParser.Destroy;
507 begin
508 SetLength(Arguments, 0);
509 end;
510
511 constructor TNamedObject.Create(const Name: string; Config: TINIFile; const Section: string);
512 begin
513 inherited Create;
514 FName:= Name;
515 FAliases:= TStringList.Create;
516 FAliases.Delimiter:= ',';
517 FAliases.DelimitedText:= FName + ',' + Config.ReadString(Section, 'Alias', '');
518 end;
519
520 constructor TMainServerConfig.Create(Config: TINIFile);
521 var i: integer; rawaddresslist: string; portlist: TStringList;
522 begin
523 inherited Create(Config.ReadString('Server', 'Name', ''), Config, 'Server');
524 FListenAddresses:= TStringList.Create;
525 FListenAddresses.Delimiter:= ',';
526
527 rawaddresslist:= Config.ReadString('Server', 'ListenAddress', '');
528 if rawaddresslist <> '' then
529 FListenAddresses.DelimitedText:= rawaddresslist
530 else begin
531 portlist:= TStringList.Create;
532 portlist.Delimiter:= ',';
533 portlist.DelimitedText:= Config.ReadString('Server', 'ListenPort', '25');
534 for i:= 0 to portlist.Count - 1 do
535 FListenAddresses.Add('0.0.0.0:' + portlist.Strings[i]);
536 portlist.Free;
537 end;
538
539 FListenAddresses6:= TStringList.Create;
540 FListenAddresses6.Delimiter:= ',';
541 FListenAddresses6.DelimitedText:= Config.ReadString('Server', 'ListenAddress6', '');
542
543 FBindAddress:= Config.ReadString('Server', 'BindAddress', '0.0.0.0');
544 FBindAddress6:= Config.ReadString('Server', 'BindAddress6', '[::]');
545
546 FDatabytes:= Config.ReadInteger('Server', 'Databytes', 1024 * 1024 * 1024);
547 FTimeOffset:= Config.ReadInteger('Server', 'TimeOffset', Config.ReadInteger('Server', 'TimeCorrection', 0) * 100);
548 FTimeOffsetStr:= MakeTimeOffsetStr(FTimeOffset);
549
550 FPolicies:= Config.ReadBool('Server', 'Policies', false);
551 FMailbox:= Config.ReadBool('Server', 'Mailbox', false);
552 FRelay:= Config.ReadBool('Server', 'Relay', false);
553 FLog:= Config.ReadBool('Server', 'Log', false);
554 end;
555
556 constructor TEMailProperties.Create;
557 begin
558 inherited Create;
559 SetSize(0);
560 WriteFlags(0);
561 end;
562
563 constructor TIPNamePair.Create(const Name, IP: string);
564 begin
565 FName:= Name;
566 FIP:= IP;
567 end;
568
569 constructor TEnvelope.Create;
570 begin
571 inherited Create;
572 FReturnPath:= '';
573 FReturnPathSpecified:= false;
574 FRelayHost:= '';
575 SetLength(FRecipients, 0);
576 end;
577
578 destructor TEnvelope.Destroy;
579 begin
580 SetLength(FRecipients, 0);
581 inherited Destroy;
582 end;
583
584
585 { Object methods: }
586
587 procedure TArgumentParser.ParseArgument(Arg: string; const AllowedPrefixes: array of string);
588 var i, n: integer; found: boolean;
589 begin
590 { Strip prefix if present. }
591 i:= 0; found:= false;
592 while ((i < Length(AllowedPrefixes)) and (not found)) do begin
593 if pos(AllowedPrefixes[i], Arg) = 1 then
594 begin
595 Delete(Arg, 1, Length(AllowedPrefixes[i]));
596 found:= true;
597 end;
598 Inc(i);
599 end;
600
601 n:= Length(Arguments);
602 SetLength(Arguments, n + 1);
603 SplitParameters(Arg, Arguments[n].Option, Arguments[n].Value, '=');
604 { To be case-insensitive: }
605 Arguments[n].Option:= UpCase(Arguments[n].Option);
606 end;
607
608 function TArgumentParser.GetArgument(ID: integer): TArgument;
609 begin
610 { No index checking... you'd better use it return value of ValidateArguments. }
611 Result:= Arguments[ID];
612 end;
613
614 function TArgumentParser.IsPresent(ArgumentName: string): boolean;
615 var i: integer;
616 begin
617 i:= 0;
618 while (i < Length(Arguments)) and (Arguments[i].Option <> UpCase(ArgumentName)) do
619 Inc(i);
620 Result:= i < Length(Arguments);
621 end;
622
623 function TArgumentParser.GetValue(ArgumentName: string; DefValue: string = ''): string;
624 var i: integer;
625 begin
626 i:= 0;
627 while (i < Length(Arguments)) and (Arguments[i].Option <> UpCase(ArgumentName)) do
628 Inc(i);
629
630 if i < Length(Arguments) then begin
631 if Arguments[i].Value <> '' then
632 Result:= Arguments[i].Value
633 else
634 Result:= DefValue;
635 end
636 else
637 Result:= DefValue;
638 end;
639
640 function TArgumentParser.ValidateArguments(ValidArguments: array of string): integer;
641 { Returns -1 if all arguments are valid. Otherwise, returns the ID of the first
642 invalid parameter. }
643 var i: integer;
644 begin
645 i:= 0;
646 while (i < Length(Arguments)) and InStringArray(Arguments[i].Option, ValidArguments) do
647 Inc(i);
648
649 if i >= Length(Arguments) then
650 Result:= -1
651 else
652 Result:= i;
653 end;
654
655
656 function TNamedObject.IsItYourName(const Name: string): boolean;
657 begin
658 Result:= FAliases.IndexOf(Name) <> -1;
659 end;
660
661
662 function TMainServerConfig.GetVersionStr: string;
663 begin
664 Result:= VERSION_STR;
665 end;
666
667
668 function TIPNamePair.Copy: TIPNamePair;
669 begin
670 Result:= TIPNamePair.Create(Name, IP);
671 end;
672
673
674 procedure TEMailProperties.SetSize(Value: longint);
675 begin
676 FSize:= Value;
677 end;
678
679 procedure TEMailProperties.WriteFlags(Value: TEMailFlags);
680 begin
681 FFlags:= Value;
682 end;
683
684 procedure TEMailProperties.SetFlag(Flag: TEMailFlags);
685 begin
686 FFlags:= FFlags or Flag;
687 end;
688
689 function TEMailProperties.HasFlag(Flag: TEMailFlags): boolean;
690 begin
691 Result:= (FFlags and Flag) = Flag;
692 end;
693
694
695 function TEnvelope.GetNumberOfRecipients: integer;
696 begin
697 Result:= Length(FRecipients);
698 end;
699
700 function TEnvelope.GetRecipient(Index: integer): TRecipient;
701 begin
702 Result:= FRecipients[Index];
703 end;
704
705 function TEnvelope.IsComplete: boolean;
706 begin
707 Result:= FReturnPathSpecified and (Length(FRecipients) > 0);
708 end;
709
710 procedure TEnvelope.AddRecipient(Address: string; Data: integer = 0; RMsg: string = '');
711 var i: integer;
712 begin
713 i:= Length(FRecipients);
714 SetLength(FRecipients, i + 1);
715 FRecipients[i].Address:= Address;
716 FRecipients[i].RMsg:= RMsg;
717 FRecipients[i].Data:= Data;
718 end;
719
720 procedure TEnvelope.AddRecipient(Recipient: TRecipient);
721 var i: integer;
722 begin
723 i:= Length(FRecipients);
724 SetLength(FRecipients, i + 1);
725 FRecipients[i]:= Recipient;
726 end;
727
728 procedure TEnvelope.SetRecipientData(Index, Data: integer; RMsg: string = '');
729 begin
730 FRecipients[Index].RMsg:= RMsg;
731 FRecipients[Index].Data:= Data;
732 end;
733
734 procedure TEnvelope.SetAllRecipientData(Data: integer; RMsg: string = '');
735 var i: integer;
736 begin
737 for i:= 0 to Length(FRecipients) - 1 do
738 SetRecipientData(i, Data, RMsg);
739 end;
740
741 procedure TEnvelope.SetReturnPath(Address: string);
742 begin
743 FReturnPath:= Address;
744 FReturnPathSpecified:= true;
745 end;
746
747 procedure TEnvelope.SetRelayHost(HostName: string);
748 begin
749 FRelayHost:= HostName;
750 end;
751
752
753 end.