2 MegaBrutal's SMTP Server (MgSMTP)
3 Copyright (C) 2010-2014 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/>.
24 uses Windows
, SysUtils
, INIFiles
, EINIFiles
,
25 Common
, Log
, Spool
, Listener
, Mailbox
, Relay
, Policies
;
28 function RegisterServiceCtrlHandlerEx(lpServiceName
: LPCSTR
; lpHandlerProc
: pointer; lpContext
: LPVOID
): SERVICE_STATUS_HANDLE
; stdcall; external 'advapi32.dll' name
'RegisterServiceCtrlHandlerExA';
29 procedure Service(Argc
: dword
; Argv
: pointer); stdcall; forward;
33 ServiceTable
: array[0..1] of TServiceTableEntry
=
36 lpServiceName
: 'MgSMTP';
37 lpServiceProc
: @Service
;
45 { For development test builds, you can add a developer comment here to
46 document what bugfix/feature are you testing with the actual build.
47 This will be logged to help you differentiate outputs of subsequent
48 builds in your logs. If left empty, it won't be added to the logs. }
49 DEVCOMMENT
= 'Testing new parameters';
54 hSCManager
, hService
: THandle
;
55 hSvcStatusHandle
: THandle
;
56 SvcStatus
: TServiceStatus
;
57 ServiceMode
, Stopping
: boolean;
60 procedure AddDevComment(Log
: TStreamLogger
);
62 if DEVCOMMENT
<> '' then begin
63 Log
.writeln('Build: ' + {$INCLUDE %DATE%} + ' ' + {$INCLUDE %TIME%});
64 Log
.writeln('Developer note: ' + DEVCOMMENT
);
68 procedure ReportSvcStatus(CurrentState
, WinExitCode
, WaitHint
: dword
);
69 { Report a service status to the Windows SCM. }
71 if ServiceMode
then begin
72 SvcStatus
.dwCurrentState
:= CurrentState
;
73 SvcStatus
.dwWaitHint
:= WaitHint
;
74 if WinExitCode
<> 0 then begin
75 SvcStatus
.dwWin32ExitCode
:= ERROR_SERVICE_SPECIFIC_ERROR
;
76 SvcStatus
.dwServiceSpecificExitCode
:= WinExitCode
;
79 SvcStatus
.dwWin32ExitCode
:= NO_ERROR
;
82 if CurrentState
= SERVICE_START_PENDING
then
83 SvcStatus
.dwControlsAccepted
:= 0
85 if GetWinMajorVersion
>= 6 then begin
86 SvcStatus
.dwControlsAccepted
:=
87 SERVICE_ACCEPT_STOP
or SERVICE_ACCEPT_SHUTDOWN
or SERVICE_ACCEPT_PRESHUTDOWN
;
90 SvcStatus
.dwControlsAccepted
:=
91 SERVICE_ACCEPT_STOP
or SERVICE_ACCEPT_SHUTDOWN
;
95 if (CurrentState
= SERVICE_RUNNING
) or (CurrentState
= SERVICE_STOPPED
) then
96 SvcStatus
.dwCheckPoint
:= 0
98 Inc(SvcStatus
.dwCheckPoint
);
100 SetServiceStatus(hSvcStatusHandle
, SvcStatus
);
104 procedure SvcCtrlHandlerEx(Ctrl
, EventType
: dword
; EventData
, Context
: pointer); stdcall;
105 { Receive a service control code from the Windows SCM, and handle it. }
108 SERVICE_CONTROL_STOP
,
109 SERVICE_CONTROL_SHUTDOWN
,
110 SERVICE_CONTROL_PRESHUTDOWN
:
112 Out.writeln('Received service control: ' + GetServiceCodeStr(Ctrl
));
113 ReportSvcStatus(SERVICE_STOP_PENDING
, 0, 1500);
117 SERVICE_CONTROL_INTERROGATE
:
118 ReportSvcStatus(SERVICE_RUNNING
, 0, 0);
121 Out.writeln('Received unknown service control: ' + IntToStr(Ctrl
));
122 ReportSvcStatus(SERVICE_STOPPED
, 1, 0);
128 procedure Service(Argc
: dword
; Argv
: pointer); stdcall;
129 var ProposedExitCode
: integer;
131 ProposedExitCode
:= 0;
136 hSvcStatusHandle
:= RegisterServiceCtrlHandlerEx('MgSMTP', @SvcCtrlHandlerEx
, nil);
138 if ServiceMode
and (hSvcStatusHandle
= 0) then
139 Out.writeln('RegisterServiceCtrlHandlerEx failed!'#13#10'Error code: ' + IntToStr(GetLastError
))
141 SvcStatus
.dwServiceType
:= SERVICE_WIN32_OWN_PROCESS
;
142 SvcStatus
.dwServiceSpecificExitCode
:= 0;
143 SvcStatus
.dwCheckPoint
:= 0;
145 ReportSvcStatus(SERVICE_START_PENDING
, 0, 1000);
147 if ServiceMode
then Out.writeln('Started in service mode.');
150 SetCurrentDir(ExtractFilePath(ParamStr(0)));
152 if not DirectoryExists('mail') then CreateDir('mail');
153 if not DirectoryExists('spool') then CreateDir('spool');
155 if FileExists('mgsmtp_server.ini') then begin
157 Config
:= TExtBoolINIFile
.Create('mgsmtp_server.ini');
159 if Config
.ReadString('Server', 'Name', '') <> '' then begin
160 MainServerConfig
:= TMainServerConfig
.Create(Config
);
161 Logger
:= TLogger
.Create(Config
);
162 MailboxManager
:= TMailboxManager
.Create(Config
);
163 RelayManager
:= TRelayManager
.Create(Config
);
164 SpoolManager
:= TSpoolManager
.Create(Config
);
165 PolicyManager
:= TPolicyManager
.Create(Config
);
167 if Config
.ReadBool('Spool', 'KeepProcessedEnvelopes', false)
168 or Config
.ReadBool('Spool', 'KeepProcessedEMails', false) then
169 if not DirectoryExists('processed') then CreateDir('processed');
173 AddDevComment(Logger
);
174 Logger
.AddStdLine('Primary server name: ' + MainServerConfig
.Name
);
175 Logger
.AddStdLine('FCrDNS policy: ' + FCrDNSPolicyToStr(PolicyManager
.FCrDNSPolicy
));
176 if MailboxManager
.DomainSpecific
then
177 Logger
.AddStdLine('Domain-specific mailbox support is enabled.');
179 if not MailboxManager
.Verify('postmaster') then
180 ProposedExitCode
:= 3;
182 if SpoolManager
.DeliveryThreadNumber
> Length(GetAlphabetStr
) then
183 ProposedExitCode
:= 4;
185 if ProposedExitCode
= 0 then begin
186 SpoolManager
.StartDeliveryThreads
;
189 ReportSvcStatus(SERVICE_RUNNING
, 0, 0);
190 while not Stopping
do Sleep(1000);
192 ReportSvcStatus(SERVICE_STOP_PENDING
, 0, SpoolManager
.ThreadWait
* 2);
195 SpoolManager
.StopDeliveryThreads
;
198 case ProposedExitCode
of
199 3: Logger
.AddStdLine('Error: Mandatory mailbox missing. Create a mailbox for "postmaster"!');
200 4: Logger
.AddStdLine('Error: The maximum allowed number of delivery threads is ' + IntToStr(Length(GetAlphabetStr
)) + '.');
202 ReportSvcStatus(SERVICE_STOPPED
, ProposedExitCode
, 0);
205 ReportSvcStatus(SERVICE_STOP_PENDING
, 0, 2000);
207 Logger
.AddStdLine('Clean shutdown.');
214 MainServerConfig
.Free
;
215 ReportSvcStatus(SERVICE_STOPPED
, 0, 0);
219 Out.writeln('Error: Server/Name is a mandatory configuration entry.'#13#10
220 + 'Please configure the application properly, refer to the manual.');
221 ReportSvcStatus(SERVICE_STOPPED
, 2, 0);
225 Out.writeln('Error: Missing configuration file.');
226 ReportSvcStatus(SERVICE_STOPPED
, 1, 0);
233 Out.writeln('MegaBrutal''s SMTP Server, version ' + VERSION_STR
+ ', ' + IntToStr(PLATFORM_BITS
) + ' bits');
234 Out.writeln('Copyright (C) 2010-2014 MegaBrutal');
238 { TODO: Process arguments here. }
242 if ParamCount
> 0 then begin
244 if UpperCase(ParamStr(1)) = '/USERMODE' then begin
245 Out.writeln('Starting MgSMTP in user mode...');
249 else if ParamStr(1) = '/?' then begin
250 Out.writeln('Supported arguments:');
251 Out.writeln('/INSTALL - registers the actual MgSMTP binary');
252 Out.writeln(' as a Windows service.');
253 Out.writeln('/UNINSTALL - unregisters the MgSMTP service.');
254 Out.writeln('/USERMODE - starts MgSMTP in user mode');
255 Out.writeln(' (not recommended, should be only');
256 Out.writeln(' used for debugging).');
258 Out.writeln('For more information on usage, see readme.txt.');
259 Out.writeln('For license details, see license.txt.');
261 Out.writeln('If you can''t find any of these files, you');
262 Out.writeln('don''t have the complete distribution of');
263 Out.writeln('this software. In that case, download a');
264 Out.writeln('proper distribution from SourceForge:');
265 Out.writeln('https://sourceforge.net/projects/mgsmtp/');
268 else if (UpperCase(ParamStr(1)) = '/INSTALL') or (UpperCase(ParamStr(1)) = '/UNINSTALL') then begin
269 { Register / unregister service. }
270 hSCManager
:= OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS
);
271 if hSCManager
<> 0 then begin
272 if UpperCase(ParamStr(1)) = '/INSTALL' then begin
273 if CreateService(hSCManager
, 'MgSMTP', 'MegaBrutal''s SMTP Server (MgSMTP)', SC_MANAGER_ALL_ACCESS
,
274 SERVICE_WIN32_OWN_PROCESS
,
275 SERVICE_AUTO_START
, SERVICE_ERROR_NORMAL
,
282 Out.writeln('Service registered successfully.')
284 Out.writeln('CreateService failed!');
286 else if UpperCase(ParamStr(1)) = '/UNINSTALL' then begin
287 hService
:= OpenService(hSCManager
, 'MgSMTP', SC_MANAGER_ALL_ACCESS
);
288 if hService
<> 0 then begin
289 if DeleteService(hService
) then
290 Out.writeln('Service unregistered successfully.')
292 Out.writeln('DeleteService failed!');
294 else Out.writeln('OpenService failed!');
296 else Out.writeln('Unknown parameter.');
298 else Out.writeln('OpenSCManager failed!');
300 else Out.writeln('Unknown parameter specified!');
304 Out.writeln('Trying to contact Service Control Manager...');
306 if not StartServiceCtrlDispatcher(ServiceTable
) then begin
308 Out.writeln('Failed!');
310 Out.writeln('You need to start MgSMTP as a service,');
311 Out.writeln('or supply proper arguments!');
312 Out.writeln('Issue with /? for more information.');