xref: /reactos/base/services/w32time/w32time.c (revision cce399e7)
1 /*
2  * PROJECT:     ReactOS W32Time Service
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     Create W32Time Service that reqularly syncs clock to Internet Time
5  * COPYRIGHT:   Copyright 2006 Ged Murphy <gedmurphy@gmail.com>
6  *              Copyright 2018 Doug Lyons
7  */
8 
9 #include "w32time.h"
10 #include <debug.h>
11 #include <strsafe.h>
12 
13 SERVICE_STATUS ServiceStatus;
14 SERVICE_STATUS_HANDLE hStatus;
15 HANDLE hStopEvent = NULL;
16 static WCHAR ServiceName[] = L"W32Time";
17 
18 int InitService(VOID);
19 
20 BOOL
21 SystemSetTime(LPSYSTEMTIME lpSystemTime)
22 {
23     HANDLE hToken;
24     DWORD PrevSize;
25     TOKEN_PRIVILEGES priv, previouspriv;
26     BOOL Ret = FALSE;
27 
28     /*
29      * Enable the SeSystemtimePrivilege privilege
30      */
31 
32     if (OpenProcessToken(GetCurrentProcess(),
33                          TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
34                          &hToken))
35     {
36         priv.PrivilegeCount = 1;
37         priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
38 
39         if (LookupPrivilegeValueW(NULL,
40                                   SE_SYSTEMTIME_NAME,
41                                   &priv.Privileges[0].Luid))
42         {
43             if (AdjustTokenPrivileges(hToken,
44                                       FALSE,
45                                       &priv,
46                                       sizeof(previouspriv),
47                                       &previouspriv,
48                                       &PrevSize) &&
49                 GetLastError() == ERROR_SUCCESS)
50             {
51                 /*
52                  * We successfully enabled it, we're permitted to change the time.
53                  */
54                 Ret = SetSystemTime(lpSystemTime);
55 
56                 /*
57                  * For the sake of security, restore the previous status again
58                  */
59                 if (previouspriv.PrivilegeCount > 0)
60                 {
61                     AdjustTokenPrivileges(hToken,
62                                           FALSE,
63                                           &previouspriv,
64                                           0,
65                                           NULL,
66                                           0);
67                 }
68             }
69         }
70         CloseHandle(hToken);
71     }
72 
73     return Ret;
74 }
75 
76 
77 /*
78  * NTP servers state the number of seconds passed since
79  * 1st Jan, 1900. The time returned from the server
80  * needs adding to that date to get the current Gregorian time
81  */
82 static DWORD
83 UpdateSystemTime(ULONG ulTime)
84 {
85     FILETIME ftNew;
86     LARGE_INTEGER li;
87     SYSTEMTIME stNew;
88 
89     /* Time at 1st Jan 1900 */
90     stNew.wYear = 1900;
91     stNew.wMonth = 1;
92     stNew.wDay = 1;
93     stNew.wHour = 0;
94     stNew.wMinute = 0;
95     stNew.wSecond = 0;
96     stNew.wMilliseconds = 0;
97 
98     /* Convert to a file time */
99     if (!SystemTimeToFileTime(&stNew, &ftNew))
100     {
101         return GetLastError();
102     }
103 
104     /* Add on the time passed since 1st Jan 1900 */
105     li = *(LARGE_INTEGER *)&ftNew;
106     li.QuadPart += (LONGLONG)10000000 * ulTime;
107     ftNew = * (FILETIME *)&li;
108 
109     /* Convert back to a system time */
110     if (!FileTimeToSystemTime(&ftNew, &stNew))
111     {
112         return GetLastError();
113     }
114 
115     if (!SystemSetTime(&stNew))
116     {
117         return GetLastError();
118     }
119 
120     return ERROR_SUCCESS;
121 }
122 
123 
124 static DWORD
125 GetIntervalSetting(VOID)
126 {
127     HKEY hKey;
128     DWORD dwData;
129     DWORD dwSize = sizeof(dwData);
130     LONG lRet;
131 
132     dwData = 0;
133     lRet = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
134                          L"SYSTEM\\CurrentControlSet\\Services\\W32Time\\TimeProviders\\NtpClient",
135                          0,
136                          KEY_QUERY_VALUE,
137                          &hKey);
138     if (lRet == ERROR_SUCCESS)
139     {
140         /*
141          * This key holds the update interval in seconds.
142          * It is useful for testing to set it to a value of 10 (Decimal).
143          * This will cause the clock to try and update every 10 seconds.
144          * So you can change the time and expect it to be set back correctly in 10-20 seconds.
145          */
146         lRet = RegQueryValueExW(hKey,
147                                 L"SpecialPollInterval",
148                                 NULL,
149                                 NULL,
150                                 (LPBYTE)&dwData,
151                                 &dwSize);
152         RegCloseKey(hKey);
153     }
154 
155     if (lRet != ERROR_SUCCESS || dwData < 120)
156         return 9 * 60 * 60; // 9 hours, because Windows uses 9 hrs, 6 mins and 8 seconds by default.
157     else
158         return dwData;
159 }
160 
161 
162 DWORD
163 SetTime(VOID)
164 {
165     ULONG ulTime;
166     LONG lRet;
167     HKEY hKey;
168     WCHAR szData[MAX_VALUE_NAME] = L"";
169     DWORD cbName = sizeof(szData);
170 
171     DPRINT("Entered SetTime.\n");
172 
173     lRet = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
174                          L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\DateTime\\Servers",
175                          0,
176                          KEY_QUERY_VALUE,
177                          &hKey);
178     if (lRet != ERROR_SUCCESS)
179     {
180         return lRet;
181     }
182 
183     lRet = RegQueryValueExW(hKey, NULL, NULL, NULL, (LPBYTE)szData, &cbName);
184     if (lRet == ERROR_SUCCESS)
185     {
186         cbName = sizeof(szData);
187         lRet = RegQueryValueExW(hKey, szData, NULL, NULL, (LPBYTE)szData, &cbName);
188     }
189 
190     RegCloseKey(hKey);
191 
192     DPRINT("Time Server is '%S'.\n", szData);
193 
194     /* Is the given string empty? */
195     if (cbName == 0 || szData[0] == '\0')
196     {
197         DPRINT("The time NTP server couldn't be found, the string is empty!\n");
198         return ERROR_INVALID_DATA;
199     }
200 
201     ulTime = GetServerTime(szData);
202 
203     if (ulTime != 0)
204     {
205         return UpdateSystemTime(ulTime);
206     }
207     else
208         return ERROR_GEN_FAILURE;
209 }
210 
211 
212 /* Control handler function */
213 VOID WINAPI
214 ControlHandler(DWORD request)
215 {
216     switch (request)
217     {
218         case SERVICE_CONTROL_STOP:
219         case SERVICE_CONTROL_SHUTDOWN:
220             DPRINT("Stopping W32Time Service\n");
221 
222             ServiceStatus.dwWin32ExitCode = 0;
223             ServiceStatus.dwCurrentState  = SERVICE_STOP_PENDING;
224             SetServiceStatus(hStatus, &ServiceStatus);
225 
226             if (hStopEvent)
227                 SetEvent(hStopEvent);
228             return;
229 
230         default:
231             break;
232     }
233 }
234 
235 
236 VOID
237 WINAPI
238 W32TmServiceMain(DWORD argc, LPWSTR *argv)
239 {
240     LONG error;
241     DWORD dwInterval;
242     HKEY hKey;
243     WCHAR szData[8];
244     DWORD cbData;
245     BOOL bNoSync;
246 
247     UNREFERENCED_PARAMETER(argc);
248     UNREFERENCED_PARAMETER(argv);
249 
250     ServiceStatus.dwServiceType             = SERVICE_WIN32;
251     ServiceStatus.dwCurrentState            = SERVICE_START_PENDING;
252     ServiceStatus.dwControlsAccepted        = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
253     ServiceStatus.dwWin32ExitCode           = 0;
254     ServiceStatus.dwServiceSpecificExitCode = 0;
255     ServiceStatus.dwCheckPoint              = 0;
256     ServiceStatus.dwWaitHint                = 0;
257 
258     hStatus = RegisterServiceCtrlHandlerW(ServiceName,
259                                           ControlHandler);
260     if (!hStatus)
261     {
262         /* Registering Control Handler failed */
263         return;
264     }
265 
266     /* Create the stop event */
267     hStopEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
268     if (hStopEvent == NULL)
269     {
270         ServiceStatus.dwCurrentState  = SERVICE_STOPPED;
271         ServiceStatus.dwWin32ExitCode = GetLastError();
272         ServiceStatus.dwControlsAccepted = 0;
273         SetServiceStatus(hStatus, &ServiceStatus);
274         return;
275     }
276 
277     /* Get the interval */
278     dwInterval = GetIntervalSetting();
279 
280     /* We report the running status to SCM. */
281     ServiceStatus.dwCurrentState = SERVICE_RUNNING;
282     SetServiceStatus(hStatus, &ServiceStatus);
283 
284     /* The service's worker loop */
285     for (;;)
286     {
287         /* The default is NoSync */
288         bNoSync = TRUE;
289 
290         /* TODO: Use RegNotifyChangeKeyValue() when implemented */
291         if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
292                           L"SYSTEM\\CurrentControlSet\\Services\\W32Time\\Parameters",
293                           0,
294                           KEY_QUERY_VALUE,
295                           &hKey) == ERROR_SUCCESS)
296         {
297             cbData = sizeof(szData);
298             RegQueryValueExW(hKey, L"Type", NULL, NULL, (LPBYTE)szData, &cbData);
299             szData[ARRAYSIZE(szData) - 1] = UNICODE_NULL; /* Avoid buffer overrun */
300             bNoSync = (_wcsicmp(szData, L"NoSync") == 0);
301             RegCloseKey(hKey);
302         }
303 
304         if (!bNoSync)
305         {
306             error = SetTime();
307             if (error != ERROR_SUCCESS)
308             {
309                 DPRINT("W32Time Service failed to set clock: 0x%08lX\n", error);
310 #if 0
311                 /*
312                  * In general, we do not want to stop this service for a single
313                  * Internet read failure but there may be other reasons for which
314                  * we really might want to stop it. Therefore this code is left here.
315                  */
316                 ServiceStatus.dwCurrentState  = SERVICE_STOPPED;
317                 ServiceStatus.dwWin32ExitCode = error;
318                 SetServiceStatus(hStatus, &ServiceStatus);
319                 return;
320 #endif
321             }
322         }
323 
324         if (WaitForSingleObject(hStopEvent, dwInterval * 1000) == WAIT_OBJECT_0)
325         {
326             CloseHandle(hStopEvent);
327             hStopEvent = NULL;
328 
329             ServiceStatus.dwCurrentState  = SERVICE_STOPPED;
330             ServiceStatus.dwWin32ExitCode = ERROR_SUCCESS;
331             ServiceStatus.dwControlsAccepted = 0;
332             SetServiceStatus(hStatus, &ServiceStatus);
333             DPRINT("Stopped W32Time Service\n");
334             return;
335         }
336     }
337 }
338 
339 
340 BOOL WINAPI
341 DllMain(HINSTANCE hinstDLL,
342         DWORD fdwReason,
343         LPVOID lpvReserved)
344 {
345     switch (fdwReason)
346     {
347         case DLL_PROCESS_ATTACH:
348             DisableThreadLibraryCalls(hinstDLL);
349             break;
350 
351         case DLL_PROCESS_DETACH:
352             break;
353     }
354 
355     return TRUE;
356 }
357 
358 
359 DWORD WINAPI
360 W32TimeSyncNow(LPCWSTR cmdline,
361                UINT blocking,
362                UINT flags)
363 {
364     DWORD result;
365     result = SetTime();
366     if (result)
367     {
368         DPRINT("W32TimeSyncNow failed and clock not set.\n");
369     }
370     else
371     {
372         DPRINT("W32TimeSyncNow succeeded and clock set.\n");
373     }
374     return result;
375 }
376