1 /*
   2  * Copyright (c) 1999, 2015, 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 
  40 #define NT_TZ_KEY               "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones"
  41 #define WIN_TZ_KEY              "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones"
  42 #define WIN_CURRENT_TZ_KEY      "System\\CurrentControlSet\\Control\\TimeZoneInformation"
  43 
  44 typedef struct _TziValue {
  45     LONG        bias;
  46     LONG        stdBias;
  47     LONG        dstBias;
  48     SYSTEMTIME  stdDate;
  49     SYSTEMTIME  dstDate;
  50 } TziValue;
  51 
  52 /*
  53  * Registry key names
  54  */
  55 static void *keyNames[] = {
  56     (void *) L"StandardName",
  57     (void *) "StandardName",
  58     (void *) L"Std",
  59     (void *) "Std"
  60 };
  61 
  62 /*
  63  * Indices to keyNames[]
  64  */
  65 #define STANDARD_NAME           0
  66 #define STD_NAME                2
  67 
  68 /*
  69  * Calls RegQueryValueEx() to get the value for the specified key. If
  70  * the platform is NT, 2000 or XP, it calls the Unicode
  71  * version. Otherwise, it calls the ANSI version and converts the
  72  * value to Unicode. In this case, it assumes that the current ANSI
  73  * Code Page is the same as the native platform code page (e.g., Code
  74  * Page 932 for the Japanese Windows systems.
  75  *
  76  * `keyIndex' is an index value to the keyNames in Unicode
  77  * (WCHAR). `keyIndex' + 1 points to its ANSI value.
  78  *
  79  * Returns the status value. ERROR_SUCCESS if succeeded, a
  80  * non-ERROR_SUCCESS value otherwise.
  81  */
  82 static LONG
  83 getValueInRegistry(HKEY hKey,
  84                    int keyIndex,
  85                    LPDWORD typePtr,
  86                    LPBYTE buf,
  87                    LPDWORD bufLengthPtr)
  88 {
  89     LONG ret;
  90     DWORD bufLength = *bufLengthPtr;
  91     char val[MAX_ZONE_CHAR];
  92     DWORD valSize;
  93     int len;
  94 
  95     *typePtr = 0;
  96     ret = RegQueryValueExW(hKey, (WCHAR *) keyNames[keyIndex], NULL,
  97                            typePtr, buf, bufLengthPtr);
  98     if (ret == ERROR_SUCCESS && *typePtr == REG_SZ) {
  99         return ret;
 100     }
 101 
 102     valSize = sizeof(val);
 103     ret = RegQueryValueExA(hKey, (char *) keyNames[keyIndex + 1], NULL,
 104                            typePtr, val, &valSize);
 105     if (ret != ERROR_SUCCESS) {
 106         return ret;
 107     }
 108     if (*typePtr != REG_SZ) {
 109         return ERROR_BADKEY;
 110     }
 111 
 112     len = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS,
 113                               (LPCSTR) val, -1,
 114                               (LPWSTR) buf, bufLength/sizeof(WCHAR));
 115     if (len <= 0) {
 116         return ERROR_BADKEY;
 117     }
 118     return ERROR_SUCCESS;
 119 }
 120 
 121 /*
 122  * Produces custom name "GMT+hh:mm" from the given bias in buffer.
 123  */
 124 static void customZoneName(LONG bias, char *buffer) {
 125     LONG gmtOffset;
 126     int sign;
 127 
 128     if (bias > 0) {
 129         gmtOffset = bias;
 130         sign = -1;
 131     } else {
 132         gmtOffset = -bias;
 133         sign = 1;
 134     }
 135     if (gmtOffset != 0) {
 136         sprintf(buffer, "GMT%c%02d:%02d",
 137                 ((sign >= 0) ? '+' : '-'),
 138                 gmtOffset / 60,
 139                 gmtOffset % 60);
 140     } else {
 141         strcpy(buffer, "GMT");
 142     }
 143 }
 144 
 145 /*
 146  * Gets the current time zone entry in the "Time Zones" registry.
 147  */
 148 static int getWinTimeZone(char *winZoneName, char *winMapID)
 149 {
 150     DYNAMIC_TIME_ZONE_INFORMATION dtzi;
 151     DWORD timeType;
 152     DWORD bufSize;
 153     DWORD val;
 154     HANDLE hKey = NULL;
 155     LONG ret; 
 156     ULONG valueType;
 157 
 158     /*
 159      * Get the dynamic time zone information so that time zone redirection
 160      * can be supported. (see JDK-7044727)
 161      */
 162     timeType = GetDynamicTimeZoneInformation(&dtzi);
 163     if (timeType == TIME_ZONE_ID_INVALID) {
 164         goto err;
 165     }
 166 
 167     /*
 168      * Make sure TimeZoneKeyName is available from the API call. If
 169      * DynamicDaylightTime is disabled, return a custom time zone name
 170      * based on the GMT offset. Otherwise, return the TimeZoneKeyName
 171      * value.
 172      */
 173     if (dtzi.TimeZoneKeyName[0] != 0) {
 174         if (dtzi.DynamicDaylightTimeDisabled) {
 175             customZoneName(dtzi.Bias, winZoneName);
 176             return VALUE_GMTOFFSET;
 177         }
 178         wcstombs(winZoneName, dtzi.TimeZoneKeyName, MAX_ZONE_CHAR);
 179         return VALUE_KEY;
 180     }
 181 
 182     /*
 183      * If TimeZoneKeyName is not available, check whether StandardName
 184      * is available to fall back to the older API GetTimeZoneInformation.
 185      * If not, directly read the value from registry keys.
 186      */
 187     if (dtzi.StandardName[0] == 0) {
 188         ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0,
 189                            KEY_READ, (PHKEY)&hKey);
 190         if (ret != ERROR_SUCCESS) {
 191             goto err;
 192         }
 193 
 194         /*
 195          * Determine if auto-daylight time adjustment is turned off.
 196          */
 197         bufSize = sizeof(val);
 198         ret = RegQueryValueExA(hKey, "DynamicDaylightTimeDisabled", NULL,
 199                                &valueType, (LPBYTE) &val, &bufSize);
 200         if (ret != ERROR_SUCCESS) {
 201             goto err;
 202         }
 203         /*
 204          * Return a custom time zone name if auto-daylight time adjustment
 205          * is disabled.
 206          */
 207         if (val == 1) {
 208             customZoneName(dtzi.Bias, winZoneName);
 209             (void) RegCloseKey(hKey);
 210             return VALUE_GMTOFFSET;
 211         }
 212 
 213         bufSize = MAX_ZONE_CHAR;
 214         ret = RegQueryValueExA(hKey, "TimeZoneKeyName", NULL,
 215                                &valueType, (LPBYTE) winZoneName, &bufSize);
 216         if (ret != ERROR_SUCCESS) {
 217             goto err;
 218         }
 219         (void) RegCloseKey(hKey);
 220         return VALUE_KEY;
 221     } else {
 222         /*
 223          * Fall back to GetTimeZoneInformation
 224          */
 225         TIME_ZONE_INFORMATION tzi;
 226         HANDLE hSubKey = NULL;
 227         DWORD nSubKeys, i;
 228         ULONG valueType;
 229         TCHAR subKeyName[MAX_ZONE_CHAR];
 230         TCHAR szValue[MAX_ZONE_CHAR];
 231         WCHAR stdNameInReg[MAX_ZONE_CHAR];
 232         TziValue tempTzi;
 233         WCHAR *stdNamePtr = tzi.StandardName;
 234         DWORD valueSize;
 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         /*
 376          * Get the "MapID" value of the registry to be able to eliminate
 377          * duplicated key names later.
 378          */
 379         valueSize = MAX_MAPID_LENGTH;
 380         ret = RegQueryValueExA(hSubKey, "MapID", NULL, &valueType, winMapID, &valueSize);
 381         (void) RegCloseKey(hSubKey);
 382         (void) RegCloseKey(hKey);
 383 
 384         if (ret != ERROR_SUCCESS) {
 385             /*
 386              * Vista doesn't have mapID. VALUE_UNKNOWN should be returned
 387              * only for Windows NT.
 388              */
 389             if (onlyMapID == 1) {
 390                 return VALUE_UNKNOWN;
 391             }
 392         }
 393     }
 394 
 395     return VALUE_KEY;
 396 
 397  err:
 398     if (hKey != NULL) {
 399         (void) RegCloseKey(hKey);
 400     }
 401     return VALUE_UNKNOWN;
 402 }
 403 
 404 /*
 405  * The mapping table file name.
 406  */
 407 #define MAPPINGS_FILE "\\lib\\tzmappings"
 408 
 409 /*
 410  * Index values for the mapping table.
 411  */
 412 #define TZ_WIN_NAME     0
 413 #define TZ_MAPID        1
 414 #define TZ_REGION       2
 415 #define TZ_JAVA_NAME    3
 416 
 417 #define TZ_NITEMS       4       /* number of items (fields) */
 418 
 419 /*
 420  * Looks up the mapping table (tzmappings) and returns a Java time
 421  * zone ID (e.g., "America/Los_Angeles") if found. Otherwise, NULL is
 422  * returned.
 423  *
 424  * value_type is one of the following values:
 425  *      VALUE_KEY for exact key matching
 426  *      VALUE_MAPID for MapID (this is
 427  *      required for the old Windows, such as NT 4.0 SP3).
 428  */
 429 static char *matchJavaTZ(const char *java_home_dir, int value_type, char *tzName,
 430                          char *mapID)
 431 {
 432     int line;
 433     int IDmatched = 0;
 434     FILE *fp;
 435     char *javaTZName = NULL;
 436     char *items[TZ_NITEMS];
 437     char *mapFileName;
 438     char lineBuffer[MAX_ZONE_CHAR * 4];
 439     int noMapID = *mapID == '\0';       /* no mapID on Vista and later */
 440 
 441     mapFileName = malloc(strlen(java_home_dir) + strlen(MAPPINGS_FILE) + 1);
 442     if (mapFileName == NULL) {
 443         return NULL;
 444     }
 445     strcpy(mapFileName, java_home_dir);
 446     strcat(mapFileName, MAPPINGS_FILE);
 447 
 448     if ((fp = fopen(mapFileName, "r")) == NULL) {
 449         jio_fprintf(stderr, "can't open %s.\n", mapFileName);
 450         free((void *) mapFileName);
 451         return NULL;
 452     }
 453     free((void *) mapFileName);
 454 
 455     line = 0;
 456     while (fgets(lineBuffer, sizeof(lineBuffer), fp) != NULL) {
 457         char *start, *idx, *endp;
 458         int itemIndex = 0;
 459 
 460         line++;
 461         start = idx = lineBuffer;
 462         endp = &lineBuffer[sizeof(lineBuffer)];
 463 
 464         /*
 465          * Ignore comment and blank lines.
 466          */
 467         if (*idx == '#' || *idx == '\n') {
 468             continue;
 469         }
 470 
 471         for (itemIndex = 0; itemIndex < TZ_NITEMS; itemIndex++) {
 472             items[itemIndex] = start;
 473             while (*idx && *idx != ':') {
 474                 if (++idx >= endp) {
 475                     goto illegal_format;
 476                 }
 477             }
 478             if (*idx == '\0') {
 479                 goto illegal_format;
 480             }
 481             *idx++ = '\0';
 482             start = idx;
 483         }
 484 
 485         if (*idx != '\n') {
 486             goto illegal_format;
 487         }
 488 
 489         if (noMapID || strcmp(mapID, items[TZ_MAPID]) == 0) {
 490             /*
 491              * When there's no mapID, we need to scan items until the
 492              * exact match is found or the end of data is detected.
 493              */
 494             if (!noMapID) {
 495                 IDmatched = 1;
 496             }
 497             if (strcmp(items[TZ_WIN_NAME], tzName) == 0) {
 498                 /*
 499                  * Found the time zone in the mapping table.
 500                  */
 501                 javaTZName = _strdup(items[TZ_JAVA_NAME]);
 502                 break;
 503             }
 504         } else {
 505             if (IDmatched == 1) {
 506                 /*
 507                  * No need to look up the mapping table further.
 508                  */
 509                 break;
 510             }
 511         }
 512     }
 513     fclose(fp);
 514 
 515     return javaTZName;
 516 
 517  illegal_format:
 518     (void) fclose(fp);
 519     jio_fprintf(stderr, "tzmappings: Illegal format at line %d.\n", line);
 520     return NULL;
 521 }
 522 
 523 /*
 524  * Detects the platform time zone which maps to a Java time zone ID.
 525  */
 526 char *findJavaTZ_md(const char *java_home_dir)
 527 {
 528     char winZoneName[MAX_ZONE_CHAR];
 529     char winMapID[MAX_MAPID_LENGTH];
 530     char *std_timezone = NULL;
 531     int  result;
 532 
 533     winMapID[0] = 0;
 534     result = getWinTimeZone(winZoneName, winMapID);
 535 
 536     if (result != VALUE_UNKNOWN) {
 537         if (result == VALUE_GMTOFFSET) {
 538             std_timezone = _strdup(winZoneName);
 539         } else {
 540             std_timezone = matchJavaTZ(java_home_dir, result,
 541                                        winZoneName, winMapID);
 542             if (std_timezone == NULL) {
 543                 std_timezone = getGMTOffsetID();
 544             }
 545         }
 546     }
 547     return std_timezone;
 548 }
 549 
 550 /**
 551  * Returns a GMT-offset-based time zone ID.
 552  */
 553 char *
 554 getGMTOffsetID()
 555 {
 556     LONG bias = 0;
 557     LONG ret;
 558     HANDLE hKey = NULL;
 559     char zonename[32];
 560 
 561     // Obtain the current GMT offset value of ActiveTimeBias.
 562     ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0,
 563                        KEY_READ, (PHKEY)&hKey);
 564     if (ret == ERROR_SUCCESS) {
 565         DWORD val;
 566         DWORD bufSize = sizeof(val);
 567         ULONG valueType = 0;
 568         ret = RegQueryValueExA(hKey, "ActiveTimeBias",
 569                                NULL, &valueType, (LPBYTE) &val, &bufSize);
 570         if (ret == ERROR_SUCCESS) {
 571             bias = (LONG) val;
 572         }
 573         (void) RegCloseKey(hKey);
 574     }
 575 
 576     // If we can't get the ActiveTimeBias value, use Bias of TimeZoneInformation.
 577     // Note: Bias doesn't reflect current daylight saving.
 578     if (ret != ERROR_SUCCESS) {
 579         TIME_ZONE_INFORMATION tzi;
 580         if (GetTimeZoneInformation(&tzi) != TIME_ZONE_ID_INVALID) {
 581             bias = tzi.Bias;
 582         }
 583     }
 584 
 585     customZoneName(bias, zonename);
 586     return _strdup(zonename);
 587 }