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