xref: /reactos/base/services/w32time/w32time.c (revision cdf90707)
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     return;
235 }
236 
237 
238 VOID
239 WINAPI
240 W32TmServiceMain(DWORD argc, LPWSTR *argv)
241 {
242     int   result;
243     DWORD dwInterval;
244 
245     UNREFERENCED_PARAMETER(argc);
246     UNREFERENCED_PARAMETER(argv);
247 
248     ServiceStatus.dwServiceType             = SERVICE_WIN32;
249     ServiceStatus.dwCurrentState            = SERVICE_START_PENDING;
250     ServiceStatus.dwControlsAccepted        = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
251     ServiceStatus.dwWin32ExitCode           = 0;
252     ServiceStatus.dwServiceSpecificExitCode = 0;
253     ServiceStatus.dwCheckPoint              = 0;
254     ServiceStatus.dwWaitHint                = 0;
255 
256     hStatus = RegisterServiceCtrlHandlerW(ServiceName,
257                                           ControlHandler);
258     if (!hStatus)
259     {
260         /* Registering Control Handler failed */
261         return;
262     }
263 
264     /* Create the stop event */
265     hStopEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
266     if (hStopEvent == NULL)
267     {
268         ServiceStatus.dwCurrentState  = SERVICE_STOPPED;
269         ServiceStatus.dwWin32ExitCode = GetLastError();
270         ServiceStatus.dwControlsAccepted = 0;
271         SetServiceStatus(hStatus, &ServiceStatus);
272         return;
273     }
274 
275     /* Get the interval */
276     dwInterval = GetIntervalSetting();
277 
278     /* We report the running status to SCM. */
279     ServiceStatus.dwCurrentState = SERVICE_RUNNING;
280     SetServiceStatus(hStatus, &ServiceStatus);
281 
282     /* The worker loop of a service */
283     for (;;)
284     {
285         result = SetTime();
286 
287         if (result)
288             DPRINT("W32Time Service failed to set clock.\n");
289         else
290             DPRINT("W32Time Service successfully set clock.\n");
291 
292         if (result)
293         {
294             /* In general we do not want to stop this service for a single
295              * Internet read failure but there may be other reasons for which
296              * we really might want to stop it.
297              * Therefore this code is left here to make it easy to stop this
298              * service when the correct conditions can be determined, but it
299              * is left commented out.
300             ServiceStatus.dwCurrentState  = SERVICE_STOPPED;
301             ServiceStatus.dwWin32ExitCode = result;
302             SetServiceStatus(hStatus, &ServiceStatus);
303             return;
304             */
305         }
306 
307         if (WaitForSingleObject(hStopEvent, dwInterval * 1000) == WAIT_OBJECT_0)
308         {
309             CloseHandle(hStopEvent);
310             hStopEvent = NULL;
311 
312             ServiceStatus.dwCurrentState  = SERVICE_STOPPED;
313             ServiceStatus.dwWin32ExitCode = ERROR_SUCCESS;
314             ServiceStatus.dwControlsAccepted = 0;
315             SetServiceStatus(hStatus, &ServiceStatus);
316             DPRINT("Stopped W32Time Service\n");
317             return;
318         }
319     }
320     return;
321 }
322 
323 
324 
325 BOOL WINAPI
326 DllMain(HINSTANCE hinstDLL,
327         DWORD fdwReason,
328         LPVOID lpvReserved)
329 {
330     switch (fdwReason)
331     {
332         case DLL_PROCESS_ATTACH:
333             DisableThreadLibraryCalls(hinstDLL);
334             break;
335 
336         case DLL_PROCESS_DETACH:
337             break;
338     }
339 
340     return TRUE;
341 }
342 
343 
344 DWORD WINAPI
345 W32TimeSyncNow(LPCWSTR cmdline,
346                UINT blocking,
347                UINT flags)
348 {
349     DWORD result;
350     result = SetTime();
351     if (result)
352     {
353         DPRINT("W32TimeSyncNow failed and clock not set.\n");
354     }
355     else
356     {
357         DPRINT("W32TimeSyncNow succeeded and clock set.\n");
358     }
359     return result;
360 }
361