xref: /reactos/base/services/w32time/w32time.c (revision e4898e6e)
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     if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
133                       L"SYSTEM\\CurrentControlSet\\Services\\W32Time\\TimeProviders\\NtpClient",
134                       0,
135                       KEY_QUERY_VALUE,
136                       &hKey) == ERROR_SUCCESS)
137     {
138         /* This key holds the update interval in seconds
139          * It is useful for testing to set it to a value of 10 (Decimal)
140          * This will cause the clock to try and update every 10 seconds
141          * So you can change the time and expect it to be set back correctly in 10-20 seconds
142          */
143         lRet = RegQueryValueExW(hKey,
144                                 L"SpecialPollInterval",
145                                 NULL,
146                                 NULL,
147                                 (LPBYTE)&dwData,
148                                 &dwSize);
149         RegCloseKey(hKey);
150     }
151 
152     if (lRet != ERROR_SUCCESS)
153         return 0;
154     else
155         return dwData;
156 }
157 
158 
159 DWORD
160 SetTime(VOID)
161 {
162     ULONG ulTime;
163     LONG lRet;
164     HKEY hKey;
165     WCHAR szData[MAX_VALUE_NAME] = L"";
166     DWORD cbName = sizeof(szData);
167 
168     DPRINT("Entered SetTime.\n");
169 
170     lRet = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
171                          L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\DateTime\\Servers",
172                          0,
173                          KEY_QUERY_VALUE,
174                          &hKey);
175     if (lRet != ERROR_SUCCESS)
176     {
177         return lRet;
178     }
179 
180     lRet = RegQueryValueExW(hKey, NULL, NULL, NULL, (LPBYTE)szData, &cbName);
181     if (lRet == ERROR_SUCCESS)
182     {
183         cbName = sizeof(szData);
184         lRet = RegQueryValueExW(hKey, szData, NULL, NULL, (LPBYTE)szData, &cbName);
185     }
186 
187     RegCloseKey(hKey);
188 
189     DPRINT("Time Server is '%S'.\n", szData);
190 
191     ulTime = GetServerTime(szData);
192 
193     if (ulTime != 0)
194     {
195         return UpdateSystemTime(ulTime);
196     }
197     else
198         return ERROR_GEN_FAILURE;
199 }
200 
201 
202 /* Control handler function */
203 VOID WINAPI
204 ControlHandler(DWORD request)
205 {
206     switch (request)
207     {
208         case SERVICE_CONTROL_STOP:
209             DPRINT("W32Time Service stopped.\n");
210 
211             ServiceStatus.dwWin32ExitCode = 0;
212             ServiceStatus.dwCurrentState  = SERVICE_STOPPED;
213             SetServiceStatus(hStatus, &ServiceStatus);
214             return;
215 
216         case SERVICE_CONTROL_SHUTDOWN:
217             DPRINT("W32Time Service stopped.\n");
218 
219             ServiceStatus.dwWin32ExitCode = 0;
220             ServiceStatus.dwCurrentState  = SERVICE_STOPPED;
221             SetServiceStatus(hStatus, &ServiceStatus);
222             return;
223 
224         default:
225             break;
226     }
227 
228     /* Report current status */
229     SetServiceStatus(hStatus, &ServiceStatus);
230 
231     return;
232 }
233 
234 
235 VOID
236 WINAPI
237 ServiceMain(DWORD argc, LPWSTR *argv)
238 {
239     int   result;
240     DWORD dwPollInterval;
241 
242     UNREFERENCED_PARAMETER(argc);
243     UNREFERENCED_PARAMETER(argv);
244 
245     ServiceStatus.dwServiceType             = SERVICE_WIN32;
246     ServiceStatus.dwCurrentState            = SERVICE_START_PENDING;
247     ServiceStatus.dwControlsAccepted        = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
248     ServiceStatus.dwWin32ExitCode           = 0;
249     ServiceStatus.dwServiceSpecificExitCode = 0;
250     ServiceStatus.dwCheckPoint              = 0;
251     ServiceStatus.dwWaitHint                = 0;
252 
253     hStatus = RegisterServiceCtrlHandlerW(ServiceName,
254                                           ControlHandler);
255     if (!hStatus)
256     {
257         /* Registering Control Handler failed */
258         return;
259     }
260 
261     /* We report the running status to SCM. */
262     ServiceStatus.dwCurrentState = SERVICE_RUNNING;
263     SetServiceStatus(hStatus, &ServiceStatus);
264 
265     /* The worker loop of a service */
266     while (ServiceStatus.dwCurrentState == SERVICE_RUNNING)
267     {
268         dwPollInterval = GetIntervalSetting();
269         result = SetTime();
270 
271         if (result)
272             DPRINT("W32Time Service failed to set clock.\n");
273         else
274             DPRINT("W32Time Service successfully set clock.\n");
275 
276         if (result)
277         {
278             /* In general we do not want to stop this service for a single
279              * Internet read failure but there may be other reasons for which
280              * we really might want to stop it.
281              * Therefore this code is left here to make it easy to stop this
282              * service when the correct conditions can be determined, but it
283              * is left commented out.
284             ServiceStatus.dwCurrentState  = SERVICE_STOPPED;
285             ServiceStatus.dwWin32ExitCode = -1;
286             SetServiceStatus(hStatus, &ServiceStatus);
287             return;
288             */
289         }
290 
291         Sleep(dwPollInterval * 1000);
292     }
293     return;
294 }
295 
296 
297 
298 BOOL WINAPI
299 DllMain(HINSTANCE hinstDLL,
300         DWORD fdwReason,
301         LPVOID lpvReserved)
302 {
303     switch (fdwReason)
304     {
305         case DLL_PROCESS_ATTACH:
306             DisableThreadLibraryCalls(hinstDLL);
307             break;
308 
309         case DLL_PROCESS_DETACH:
310             break;
311     }
312 
313     return TRUE;
314 }
315 
316 
317 DWORD WINAPI
318 W32TimeSyncNow(LPCWSTR cmdline,
319                UINT blocking,
320                UINT flags)
321 {
322     DWORD result;
323     result = SetTime();
324     if (result)
325     {
326         DPRINT("W32TimeSyncNow failed and clock not set.\n");
327     }
328     else
329     {
330         DPRINT("W32TimeSyncNow succeeded and clock set.\n");
331     }
332     return result;
333 }
334