1 /*
2  * Copyright (c) 1999, 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 #include <windows.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include "jvm.h"
30 #include "TimeZone_md.h"
31 
32 #define VALUE_UNKNOWN           0
33 #define VALUE_KEY               1
34 #define VALUE_MAPID             2
35 #define VALUE_GMTOFFSET         3
36 
37 #define MAX_ZONE_CHAR           256
38 #define MAX_MAPID_LENGTH        32
39 #define MAX_REGION_LENGTH       4
40 
41 #define NT_TZ_KEY               "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones"
42 #define WIN_TZ_KEY              "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones"
43 #define WIN_CURRENT_TZ_KEY      "System\\CurrentControlSet\\Control\\TimeZoneInformation"
44 
45 typedef struct _TziValue {
46     LONG        bias;
47     LONG        stdBias;
48     LONG        dstBias;
49     SYSTEMTIME  stdDate;
50     SYSTEMTIME  dstDate;
51 } TziValue;
52 
53 /*
54  * Registry key names
55  */
56 static void *keyNames[] = {
57     (void *) L"StandardName",
58     (void *) "StandardName",
59     (void *) L"Std",
60     (void *) "Std"
61 };
62 
63 /*
64  * Indices to keyNames[]
65  */
66 #define STANDARD_NAME           0
67 #define STD_NAME                2
68 
69 /*
70  * Calls RegQueryValueEx() to get the value for the specified key. If
71  * the platform is NT, 2000 or XP, it calls the Unicode
72  * version. Otherwise, it calls the ANSI version and converts the
73  * value to Unicode. In this case, it assumes that the current ANSI
74  * Code Page is the same as the native platform code page (e.g., Code
75  * Page 932 for the Japanese Windows systems.
76  *
77  * `keyIndex' is an index value to the keyNames in Unicode
78  * (WCHAR). `keyIndex' + 1 points to its ANSI value.
79  *
80  * Returns the status value. ERROR_SUCCESS if succeeded, a
81  * non-ERROR_SUCCESS value otherwise.
82  */
83 static LONG
getValueInRegistry(HKEY hKey,int keyIndex,LPDWORD typePtr,LPBYTE buf,LPDWORD bufLengthPtr)84 getValueInRegistry(HKEY hKey,
85                    int keyIndex,
86                    LPDWORD typePtr,
87                    LPBYTE buf,
88                    LPDWORD bufLengthPtr)
89 {
90     LONG ret;
91     DWORD bufLength = *bufLengthPtr;
92     char val[MAX_ZONE_CHAR];
93     DWORD valSize;
94     int len;
95 
96     *typePtr = 0;
97     ret = RegQueryValueExW(hKey, (WCHAR *) keyNames[keyIndex], NULL,
98                            typePtr, buf, bufLengthPtr);
99     if (ret == ERROR_SUCCESS && *typePtr == REG_SZ) {
100         return ret;
101     }
102 
103     valSize = sizeof(val);
104     ret = RegQueryValueExA(hKey, (char *) keyNames[keyIndex + 1], NULL,
105                            typePtr, val, &valSize);
106     if (ret != ERROR_SUCCESS) {
107         return ret;
108     }
109     if (*typePtr != REG_SZ) {
110         return ERROR_BADKEY;
111     }
112 
113     len = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS,
114                               (LPCSTR) val, -1,
115                               (LPWSTR) buf, bufLength/sizeof(WCHAR));
116     if (len <= 0) {
117         return ERROR_BADKEY;
118     }
119     return ERROR_SUCCESS;
120 }
121 
122 /*
123  * Produces custom name "GMT+hh:mm" from the given bias in buffer.
124  */
customZoneName(LONG bias,char * buffer)125 static void customZoneName(LONG bias, char *buffer) {
126     LONG gmtOffset;
127     int sign;
128 
129     if (bias > 0) {
130         gmtOffset = bias;
131         sign = -1;
132     } else {
133         gmtOffset = -bias;
134         sign = 1;
135     }
136     if (gmtOffset != 0) {
137         sprintf(buffer, "GMT%c%02d:%02d",
138                 ((sign >= 0) ? '+' : '-'),
139                 gmtOffset / 60,
140                 gmtOffset % 60);
141     } else {
142         strcpy(buffer, "GMT");
143     }
144 }
145 
146 /*
147  * Gets the current time zone entry in the "Time Zones" registry.
148  */
getWinTimeZone(char * winZoneName)149 static int getWinTimeZone(char *winZoneName)
150 {
151     DYNAMIC_TIME_ZONE_INFORMATION dtzi;
152     DWORD timeType;
153     DWORD bufSize;
154     DWORD val;
155     HANDLE hKey = NULL;
156     LONG ret;
157     ULONG valueType;
158 
159     /*
160      * Get the dynamic time zone information so that time zone redirection
161      * can be supported. (see JDK-7044727)
162      */
163     timeType = GetDynamicTimeZoneInformation(&dtzi);
164     if (timeType == TIME_ZONE_ID_INVALID) {
165         goto err;
166     }
167 
168     /*
169      * Make sure TimeZoneKeyName is available from the API call. If
170      * DynamicDaylightTime is disabled, return a custom time zone name
171      * based on the GMT offset. Otherwise, return the TimeZoneKeyName
172      * value.
173      */
174     if (dtzi.TimeZoneKeyName[0] != 0) {
175         if (dtzi.DynamicDaylightTimeDisabled) {
176             customZoneName(dtzi.Bias, winZoneName);
177             return VALUE_GMTOFFSET;
178         }
179         wcstombs(winZoneName, dtzi.TimeZoneKeyName, MAX_ZONE_CHAR);
180         return VALUE_KEY;
181     }
182 
183     /*
184      * If TimeZoneKeyName is not available, check whether StandardName
185      * is available to fall back to the older API GetTimeZoneInformation.
186      * If not, directly read the value from registry keys.
187      */
188     if (dtzi.StandardName[0] == 0) {
189         ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0,
190                            KEY_READ, (PHKEY)&hKey);
191         if (ret != ERROR_SUCCESS) {
192             goto err;
193         }
194 
195         /*
196          * Determine if auto-daylight time adjustment is turned off.
197          */
198         bufSize = sizeof(val);
199         ret = RegQueryValueExA(hKey, "DynamicDaylightTimeDisabled", NULL,
200                                &valueType, (LPBYTE) &val, &bufSize);
201         if (ret != ERROR_SUCCESS) {
202             goto err;
203         }
204         /*
205          * Return a custom time zone name if auto-daylight time adjustment
206          * is disabled.
207          */
208         if (val == 1) {
209             customZoneName(dtzi.Bias, winZoneName);
210             (void) RegCloseKey(hKey);
211             return VALUE_GMTOFFSET;
212         }
213 
214         bufSize = MAX_ZONE_CHAR;
215         ret = RegQueryValueExA(hKey, "TimeZoneKeyName", NULL,
216                                &valueType, (LPBYTE) winZoneName, &bufSize);
217         if (ret != ERROR_SUCCESS) {
218             goto err;
219         }
220         (void) RegCloseKey(hKey);
221         return VALUE_KEY;
222     } else {
223         /*
224          * Fall back to GetTimeZoneInformation
225          */
226         TIME_ZONE_INFORMATION tzi;
227         HANDLE hSubKey = NULL;
228         DWORD nSubKeys, i;
229         ULONG valueType;
230         TCHAR subKeyName[MAX_ZONE_CHAR];
231         TCHAR szValue[MAX_ZONE_CHAR];
232         WCHAR stdNameInReg[MAX_ZONE_CHAR];
233         TziValue tempTzi;
234         WCHAR *stdNamePtr = tzi.StandardName;
235         int onlyMapID;
236 
237         timeType = GetTimeZoneInformation(&tzi);
238         if (timeType == TIME_ZONE_ID_INVALID) {
239             goto err;
240         }
241 
242         ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0,
243                            KEY_READ, (PHKEY)&hKey);
244         if (ret == ERROR_SUCCESS) {
245             /*
246              * Determine if auto-daylight time adjustment is turned off.
247              */
248             bufSize = sizeof(val);
249             ret = RegQueryValueExA(hKey, "DynamicDaylightTimeDisabled", NULL,
250                                    &valueType, (LPBYTE) &val, &bufSize);
251             if (ret == ERROR_SUCCESS) {
252                 if (val == 1 && tzi.DaylightDate.wMonth != 0) {
253                     (void) RegCloseKey(hKey);
254                     customZoneName(tzi.Bias, winZoneName);
255                     return VALUE_GMTOFFSET;
256                 }
257             }
258 
259             /*
260              * Win32 problem: If the length of the standard time name is equal
261              * to (or probably longer than) 32 in the registry,
262              * GetTimeZoneInformation() on NT returns a null string as its
263              * standard time name. We need to work around this problem by
264              * getting the same information from the TimeZoneInformation
265              * registry.
266              */
267             if (tzi.StandardName[0] == 0) {
268                 bufSize = sizeof(stdNameInReg);
269                 ret = getValueInRegistry(hKey, STANDARD_NAME, &valueType,
270                                          (LPBYTE) stdNameInReg, &bufSize);
271                 if (ret != ERROR_SUCCESS) {
272                     goto err;
273                 }
274                 stdNamePtr = stdNameInReg;
275             }
276             (void) RegCloseKey(hKey);
277         }
278 
279         /*
280          * Open the "Time Zones" registry.
281          */
282         ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, NT_TZ_KEY, 0, KEY_READ, (PHKEY)&hKey);
283         if (ret != ERROR_SUCCESS) {
284             ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_TZ_KEY, 0, KEY_READ, (PHKEY)&hKey);
285             /*
286              * If both failed, then give up.
287              */
288             if (ret != ERROR_SUCCESS) {
289                 return VALUE_UNKNOWN;
290             }
291         }
292 
293         /*
294          * Get the number of subkeys of the "Time Zones" registry for
295          * enumeration.
296          */
297         ret = RegQueryInfoKey(hKey, NULL, NULL, NULL, &nSubKeys,
298                               NULL, NULL, NULL, NULL, NULL, NULL, NULL);
299         if (ret != ERROR_SUCCESS) {
300             goto err;
301         }
302 
303         /*
304          * Compare to the "Std" value of each subkey and find the entry that
305          * matches the current control panel setting.
306          */
307         onlyMapID = 0;
308         for (i = 0; i < nSubKeys; ++i) {
309             DWORD size = sizeof(subKeyName);
310             ret = RegEnumKeyEx(hKey, i, subKeyName, &size, NULL, NULL, NULL, NULL);
311             if (ret != ERROR_SUCCESS) {
312                 goto err;
313             }
314             ret = RegOpenKeyEx(hKey, subKeyName, 0, KEY_READ, (PHKEY)&hSubKey);
315             if (ret != ERROR_SUCCESS) {
316                 goto err;
317             }
318 
319             size = sizeof(szValue);
320             ret = getValueInRegistry(hSubKey, STD_NAME, &valueType,
321                                      szValue, &size);
322             if (ret != ERROR_SUCCESS) {
323                 /*
324                  * NT 4.0 SP3 fails here since it doesn't have the "Std"
325                  * entry in the Time Zones registry.
326                  */
327                 RegCloseKey(hSubKey);
328                 onlyMapID = 1;
329                 ret = RegOpenKeyExW(hKey, stdNamePtr, 0, KEY_READ, (PHKEY)&hSubKey);
330                 if (ret != ERROR_SUCCESS) {
331                     goto err;
332                 }
333                 break;
334             }
335 
336             if (wcscmp((WCHAR *)szValue, stdNamePtr) == 0) {
337                 /*
338                  * Some localized Win32 platforms use a same name to
339                  * different time zones. So, we can't rely only on the name
340                  * here. We need to check GMT offsets and transition dates
341                  * to make sure it's the registry of the current time
342                  * zone.
343                  */
344                 DWORD tziValueSize = sizeof(tempTzi);
345                 ret = RegQueryValueEx(hSubKey, "TZI", NULL, &valueType,
346                                       (unsigned char *) &tempTzi, &tziValueSize);
347                 if (ret == ERROR_SUCCESS) {
348                     if ((tzi.Bias != tempTzi.bias) ||
349                         (memcmp((const void *) &tzi.StandardDate,
350                                 (const void *) &tempTzi.stdDate,
351                                 sizeof(SYSTEMTIME)) != 0)) {
352                         goto out;
353                     }
354 
355                     if (tzi.DaylightBias != 0) {
356                         if ((tzi.DaylightBias != tempTzi.dstBias) ||
357                             (memcmp((const void *) &tzi.DaylightDate,
358                                     (const void *) &tempTzi.dstDate,
359                                     sizeof(SYSTEMTIME)) != 0)) {
360                             goto out;
361                         }
362                     }
363                 }
364 
365                 /*
366                  * found matched record, terminate search
367                  */
368                 strcpy(winZoneName, subKeyName);
369                 break;
370             }
371         out:
372             (void) RegCloseKey(hSubKey);
373         }
374 
375         (void) RegCloseKey(hKey);
376     }
377 
378     return VALUE_KEY;
379 
380  err:
381     if (hKey != NULL) {
382         (void) RegCloseKey(hKey);
383     }
384     return VALUE_UNKNOWN;
385 }
386 
387 /*
388  * The mapping table file name.
389  */
390 #define MAPPINGS_FILE "\\lib\\tzmappings"
391 
392 /*
393  * Index values for the mapping table.
394  */
395 #define TZ_WIN_NAME     0
396 #define TZ_REGION       1
397 #define TZ_JAVA_NAME    2
398 
399 #define TZ_NITEMS       3       /* number of items (fields) */
400 
401 /*
402  * Looks up the mapping table (tzmappings) and returns a Java time
403  * zone ID (e.g., "America/Los_Angeles") if found. Otherwise, NULL is
404  * returned.
405  */
matchJavaTZ(const char * java_home_dir,char * tzName)406 static char *matchJavaTZ(const char *java_home_dir, char *tzName)
407 {
408     int line;
409     int IDmatched = 0;
410     FILE *fp;
411     char *javaTZName = NULL;
412     char *items[TZ_NITEMS];
413     char *mapFileName;
414     char lineBuffer[MAX_ZONE_CHAR * 4];
415     int offset = 0;
416     const char* errorMessage = "unknown error";
417     char region[MAX_REGION_LENGTH];
418 
419     // Get the user's location
420     if (GetGeoInfo(GetUserGeoID(GEOCLASS_NATION),
421             GEO_ISO2, region, MAX_REGION_LENGTH, 0) == 0) {
422         // If GetGeoInfo fails, fallback to LCID's country
423         LCID lcid = GetUserDefaultLCID();
424         if (GetLocaleInfo(lcid,
425                           LOCALE_SISO3166CTRYNAME, region, MAX_REGION_LENGTH) == 0 &&
426             GetLocaleInfo(lcid,
427                           LOCALE_SISO3166CTRYNAME2, region, MAX_REGION_LENGTH) == 0) {
428             region[0] = '\0';
429         }
430     }
431 
432     mapFileName = malloc(strlen(java_home_dir) + strlen(MAPPINGS_FILE) + 1);
433     if (mapFileName == NULL) {
434         return NULL;
435     }
436     strcpy(mapFileName, java_home_dir);
437     strcat(mapFileName, MAPPINGS_FILE);
438 
439     if ((fp = fopen(mapFileName, "r")) == NULL) {
440         jio_fprintf(stderr, "can't open %s.\n", mapFileName);
441         free((void *) mapFileName);
442         return NULL;
443     }
444     free((void *) mapFileName);
445 
446     line = 0;
447     while (fgets(lineBuffer, sizeof(lineBuffer), fp) != NULL) {
448         char *start, *idx, *endp;
449         int itemIndex = 0;
450 
451         line++;
452         start = idx = lineBuffer;
453         endp = &lineBuffer[sizeof(lineBuffer)];
454 
455         /*
456          * Ignore comment and blank lines.
457          */
458         if (*idx == '#' || *idx == '\n') {
459             continue;
460         }
461 
462         for (itemIndex = 0; itemIndex < TZ_NITEMS; itemIndex++) {
463             items[itemIndex] = start;
464             while (*idx && *idx != ':') {
465                 if (++idx >= endp) {
466                     errorMessage = "premature end of line";
467                     offset = (int)(idx - lineBuffer);
468                     goto illegal_format;
469                 }
470             }
471             if (*idx == '\0') {
472                 errorMessage = "illegal null character found";
473                 offset = (int)(idx - lineBuffer);
474                 goto illegal_format;
475             }
476             *idx++ = '\0';
477             start = idx;
478         }
479 
480         if (*idx != '\n') {
481             errorMessage = "illegal non-newline character found";
482             offset = (int)(idx - lineBuffer);
483             goto illegal_format;
484         }
485 
486         /*
487          * We need to scan items until the
488          * exact match is found or the end of data is detected.
489          */
490         if (strcmp(items[TZ_WIN_NAME], tzName) == 0) {
491             /*
492              * Found the time zone in the mapping table.
493              * Check the region code and select the appropriate entry
494              */
495             if (strcmp(items[TZ_REGION], region) == 0 ||
496                 strcmp(items[TZ_REGION], "001") == 0) {
497                 javaTZName = _strdup(items[TZ_JAVA_NAME]);
498                 break;
499             }
500         }
501     }
502     fclose(fp);
503 
504     return javaTZName;
505 
506  illegal_format:
507     (void) fclose(fp);
508     jio_fprintf(stderr, "Illegal format in tzmappings file: %s at line %d, offset %d.\n",
509                 errorMessage, line, offset);
510     return NULL;
511 }
512 
513 /*
514  * Detects the platform time zone which maps to a Java time zone ID.
515  */
findJavaTZ_md(const char * java_home_dir)516 char *findJavaTZ_md(const char *java_home_dir)
517 {
518     char winZoneName[MAX_ZONE_CHAR];
519     char *std_timezone = NULL;
520     int  result;
521 
522     result = getWinTimeZone(winZoneName);
523 
524     if (result != VALUE_UNKNOWN) {
525         if (result == VALUE_GMTOFFSET) {
526             std_timezone = _strdup(winZoneName);
527         } else {
528             std_timezone = matchJavaTZ(java_home_dir, winZoneName);
529             if (std_timezone == NULL) {
530                 std_timezone = getGMTOffsetID();
531             }
532         }
533     }
534     return std_timezone;
535 }
536 
537 /**
538  * Returns a GMT-offset-based time zone ID.
539  */
540 char *
getGMTOffsetID()541 getGMTOffsetID()
542 {
543     LONG bias = 0;
544     LONG ret;
545     HANDLE hKey = NULL;
546     char zonename[32];
547 
548     // Obtain the current GMT offset value of ActiveTimeBias.
549     ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0,
550                        KEY_READ, (PHKEY)&hKey);
551     if (ret == ERROR_SUCCESS) {
552         DWORD val;
553         DWORD bufSize = sizeof(val);
554         ULONG valueType = 0;
555         ret = RegQueryValueExA(hKey, "ActiveTimeBias",
556                                NULL, &valueType, (LPBYTE) &val, &bufSize);
557         if (ret == ERROR_SUCCESS) {
558             bias = (LONG) val;
559         }
560         (void) RegCloseKey(hKey);
561     }
562 
563     // If we can't get the ActiveTimeBias value, use Bias of TimeZoneInformation.
564     // Note: Bias doesn't reflect current daylight saving.
565     if (ret != ERROR_SUCCESS) {
566         TIME_ZONE_INFORMATION tzi;
567         if (GetTimeZoneInformation(&tzi) != TIME_ZONE_ID_INVALID) {
568             bias = tzi.Bias;
569         }
570     }
571 
572     customZoneName(bias, zonename);
573     return _strdup(zonename);
574 }
575