/* * Copyright (c) 2023 Valtteri Koskivuori * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* TODO: - Display possible errors in cdev */ #include #include #include "MacNTP.h" #include "ShowInitIcon.h" #include "rsrcid.h" // This enables DebugStr() printouts to aid development. // This also disables the system time check, so it tries NTP // on every boot. //#define DEBUGGING void dump_error(enum MacNTPError error, OSErr oserror); int TryMacNTP(Str255 ntp1, Str255 ntp2, Str255 utc); int is_number(char c); int validate_utc(Str255 utc_str); int utcstr_to_mins(Str255 utc, long *out); #define SECONDS_TO_02_09_1996 2924510400ul Handle g_mtcp_init_handle; void dump_error(enum MacNTPError error, OSErr oserror) { Str255 numstr; StringHandle err_str; err_str = GetString(ERR_STR_ID); if (!err_str) { SysBeep(10); SysBeep(1); return; } SetHandleSize(err_str, 256); HLock(err_str); NumToString(oserror, numstr); switch (error) { case Success: SetString(err_str, "\p"); //FIXME: Bit dumb to write this every time? break; case MacTCPInitFailed: SetString(err_str, "\pMacTCP init failed"); break; case InvalidURL: SetString(err_str, "\pInvalid URL"); break; case BadNtpStructSize: SetString(err_str, "\pBad NTP struct size"); break; case UDPCreateFailed: SetString(err_str, "\pUDPCreate failed"); break; case UDPSendFailed: SetString(err_str, "\pUDPSend failed"); break; case UDPRcvFailed: SetString(err_str, "\pUDPRcv failed"); break; case InvalidNTPResponse: SetString(err_str, "\pInvalid NTP response"); break; case OriginTimestampMismatch: SetString(err_str, "\pOrigin timestamp mismatch"); break; case DNSResolveFailed: SetString(err_str, "\pDNS resolve failed"); break; case ClockWriteFailed: SetString(err_str, "\pClock write failed"); break; case ClockReadFailed: SetString(err_str, "\pClock read failed"); break; case UDPRcvTimedOut: SetString(err_str, "\pUDPRcv timed out"); break; case PacketParamNull: SetString(err_str, "\pPacket param null"); break; } HUnlock(err_str); ChangedResource(err_str); ReleaseResource(err_str); UpdateResFile(CurResFile()); } //FIXME: This is duplicated from cdev.c, but I didn't want to //make a library and all the extra build steps just for this. //Maybe we'll just stick it in MacNTPLib and figure out linkage somehow int is_number(char c) { return c >> 4 == 3 && (c & 0x0F) < 0xA; } int validate_utc(Str255 utc_str) { // Str255 is a pointer to a pascal string, where // the first byte is the string length. char scratch[6]; short strlen; char *str; strlen = utc_str[0]; str = (char *)&utc_str[1]; if (strlen != 6) return false; if (str[0] != '+' && str[0] != '-') return false; if (!is_number(str[1]) || !is_number(str[2])) return false; if (str[3] != ':') return false; if (!is_number(str[4]) || !is_number(str[5])) return false; return true; } int utcstr_to_mins(Str255 utc, long *out) { long minutes; if (!validate_utc(utc)) return false; minutes = 0; minutes += ((utc[2] - 0x30) * 10) * 60; minutes += (utc[3] - 0x30) * 60; minutes += (utc[5] - 0x30) * 10; minutes += (utc[6] - 0x30); if (utc[1] == '-') minutes = -minutes; if (out) *out = minutes; return true; } int TryMacNTP(Str255 ntp1, Str255 ntp2, Str255 utc) { struct ntp_packet payload; enum MacNTPError error; OSErr oserr = 0; minutes_t utc_offset; if (!utc[0]) return 1; if (!utcstr_to_mins(utc, &utc_offset)) return 1; if (!ntp1[0]) return 1; PtoCstr(ntp1); error = MacNTPFetchTime((char *)(ntp1), &payload, &oserr, utc_offset); if (error == DNSResolveFailed) SysBeep(1); // If the first lookup fails and we have a fallback, try it. if (error == DNSResolveFailed && ntp2[0]) { PtoCstr(ntp2); error = MacNTPFetchTime((char *)(ntp2), &payload, &oserr, utc_offset); } dump_error(error, oserr); if (error) return 1; error = MacNTPSetSystemTime(&payload, utc_offset); dump_error(error, oserr); if (error) return 1; return 0; } void main() { Ptr our_init_ptr; unsigned long systime; StringHandle ntp1; StringHandle ntp2; StringHandle utc; // Offset in minutes as pstr long answer; OSErr gestalt_err; asm { move.L A0, our_init_ptr; } RememberA0(); SetUpA4(); // This is already locked in the resource attrs g_mtcp_init_handle = RecoverHandle(our_init_ptr); // First we check if MacTCP is available // This also assumes Gestalt support gestalt_err = Gestalt(gestaltVersion, &answer); if (gestalt_err) { // Gestalt not available, it was introduced in 6.0.4 // And MacTCP in 6.0.3, so we're just going to assume // the user has at least 6.0.8 ShowInitIcon(MACNTP_ICN_ID_FAILURE, true); goto skip; } if (Gestalt('mtcp', nil)) { // MacTCP not found ShowInitIcon(MACNTP_ICN_ID_FAILURE, true); goto skip; } ntp1 = GetString(NTP1_STR_ID); ntp2 = GetString(NTP2_STR_ID); utc = GetString(UTC_STR_ID); if (!ntp1 || !ntp2 || !utc) { #ifdef DEBUGGING DebugStr("\pSTR rsrcs missing"); #endif ShowInitIcon(MACNTP_ICN_ID_FAILURE, true); goto skip; } /*if (!*ntp1[0] && !*ntp2[0]) { #ifdef DEBUGGING DebugStr("\pBoth NTP urls empty"); #endif ShowInitIcon(MACNTP_ICN_ID_FAILURE, true); goto skip; }*/ HLock(ntp1); HLock(ntp2); HLock(utc); //TODO: Move this up a bit #ifndef DEBUGGING // We only try to update the clock if we're well into the 20th century ReadDateTime((unsigned long *)&systime); if (systime > SECONDS_TO_02_09_1996) { ShowInitIcon(MACNTP_ICN_ID_SUCCESS, true); goto skip; } else { ShowInitIcon(MACNTP_ICN_ID_PENDING, false); } #else ShowInitIcon(MACNTP_ICN_ID_PENDING, false); #endif if (!TryMacNTP(*ntp1, *ntp2, *utc)) { ShowInitIcon(MACNTP_ICN_ID_SUCCESS, true); } else { ShowInitIcon(MACNTP_ICN_ID_FAILURE, true); } // I have no idea if unlock is needed here. Maybe not. HUnlock(ntp1); //DisposHandle(ntp1); HUnlock(ntp2); // For whatever reason, System 6 crashes on boot if we // try to dispose this specific handle. Okay. //DisposHandle(ntp2); HUnlock(utc); //DisposHandle(utc); HUnlock(g_mtcp_init_handle); skip: error: RestoreA4(); }