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