| 1 |
/* |
| 2 |
* Copyright (c) 2023 Valtteri Koskivuori <vkoskiv@gmail.com> |
| 3 |
* |
| 4 |
* Permission to use, copy, modify, and distribute this software for any |
| 5 |
* purpose with or without fee is hereby granted, provided that the above |
| 6 |
* copyright notice and this permission notice appear in all copies. |
| 7 |
* |
| 8 |
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| 9 |
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| 10 |
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| 11 |
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| 12 |
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| 13 |
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
| 14 |
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| 15 |
*/ |
| 16 |
|
| 17 |
/* |
| 18 |
TODO: |
| 19 |
- Display possible errors in cdev |
| 20 |
*/ |
| 21 |
|
| 22 |
#include <SetUpA4.h> |
| 23 |
#include <GestaltEqu.h> |
| 24 |
#include "MacNTP.h" |
| 25 |
#include "ShowInitIcon.h" |
| 26 |
#include "rsrcid.h" |
| 27 |
|
| 28 |
// This enables DebugStr() printouts to aid development. |
| 29 |
// This also disables the system time check, so it tries NTP |
| 30 |
// on every boot. |
| 31 |
//#define DEBUGGING |
| 32 |
|
| 33 |
void dump_error(enum MacNTPError error, OSErr oserror); |
| 34 |
int TryMacNTP(Str255 ntp1, Str255 ntp2, Str255 utc); |
| 35 |
int is_number(char c); |
| 36 |
int validate_utc(Str255 utc_str); |
| 37 |
int utcstr_to_mins(Str255 utc, long *out); |
| 38 |
|
| 39 |
#define SECONDS_TO_02_09_1996 2924510400ul |
| 40 |
|
| 41 |
Handle g_mtcp_init_handle; |
| 42 |
|
| 43 |
void dump_error(enum MacNTPError error, OSErr oserror) { |
| 44 |
// Str255 numstr; |
| 45 |
StringHandle err_str; |
| 46 |
err_str = GetString(ERR_STR_ID); |
| 47 |
if (!err_str) { |
| 48 |
return; |
| 49 |
} |
| 50 |
SetHandleSize(err_str, 256); |
| 51 |
HLock(err_str); |
| 52 |
// NumToString(oserror, numstr); |
| 53 |
switch (error) { |
| 54 |
case Success: |
| 55 |
SetString(err_str, "\p"); //FIXME: Bit dumb to write this every time? |
| 56 |
break; |
| 57 |
case MacTCPInitFailed: |
| 58 |
SetString(err_str, "\pMacTCP init failed"); |
| 59 |
break; |
| 60 |
case MacTCPNotFound: |
| 61 |
SetString(err_str, "\pMacTCP not found"); |
| 62 |
break; |
| 63 |
case InvalidURL: |
| 64 |
SetString(err_str, "\pInvalid URL"); |
| 65 |
break; |
| 66 |
case BadNtpStructSize: |
| 67 |
SetString(err_str, "\pBad NTP struct size"); |
| 68 |
break; |
| 69 |
case UDPCreateFailed: |
| 70 |
SetString(err_str, "\pUDPCreate failed"); |
| 71 |
break; |
| 72 |
case UDPSendFailed: |
| 73 |
SetString(err_str, "\pUDPSend failed"); |
| 74 |
break; |
| 75 |
case UDPRcvFailed: |
| 76 |
SetString(err_str, "\pUDPRcv failed"); |
| 77 |
break; |
| 78 |
case InvalidNTPResponse: |
| 79 |
SetString(err_str, "\pInvalid NTP response"); |
| 80 |
break; |
| 81 |
case OriginTimestampMismatch: |
| 82 |
SetString(err_str, "\pOrigin timestamp mismatch"); |
| 83 |
break; |
| 84 |
case DNSResolveFailed: |
| 85 |
SetString(err_str, "\pDNS resolve failed"); |
| 86 |
break; |
| 87 |
case GestaltMissing: |
| 88 |
SetString(err_str, "\pMissing Gestalt, MacNTP requires >=6.0.8"); |
| 89 |
break; |
| 90 |
case ClockWriteFailed: |
| 91 |
SetString(err_str, "\pClock write failed"); |
| 92 |
break; |
| 93 |
case ClockReadFailed: |
| 94 |
SetString(err_str, "\pClock read failed"); |
| 95 |
break; |
| 96 |
case UDPRcvTimedOut: |
| 97 |
SetString(err_str, "\pUDPRcv timed out"); |
| 98 |
break; |
| 99 |
case PacketParamNull: |
| 100 |
SetString(err_str, "\pPacket param null"); |
| 101 |
break; |
| 102 |
} |
| 103 |
HUnlock(err_str); |
| 104 |
ChangedResource(err_str); |
| 105 |
ReleaseResource(err_str); |
| 106 |
UpdateResFile(CurResFile()); |
| 107 |
} |
| 108 |
|
| 109 |
//FIXME: This is duplicated from cdev.c, but I didn't want to |
| 110 |
//make a library and all the extra build steps just for this. |
| 111 |
//Maybe we'll just stick it in MacNTPLib and figure out linkage somehow |
| 112 |
int is_number(char c) { |
| 113 |
return c >> 4 == 3 && (c & 0x0F) < 0xA; |
| 114 |
} |
| 115 |
|
| 116 |
int validate_utc(Str255 utc_str) { |
| 117 |
// Str255 is a pointer to a pascal string, where |
| 118 |
// the first byte is the string length. |
| 119 |
char scratch[6]; |
| 120 |
short strlen; |
| 121 |
char *str; |
| 122 |
strlen = utc_str[0]; |
| 123 |
str = (char *)&utc_str[1]; |
| 124 |
if (strlen != 6) return false; |
| 125 |
if (str[0] != '+' && str[0] != '-') return false; |
| 126 |
if (!is_number(str[1]) || !is_number(str[2])) return false; |
| 127 |
if (str[3] != ':') return false; |
| 128 |
if (!is_number(str[4]) || !is_number(str[5])) return false; |
| 129 |
|
| 130 |
return true; |
| 131 |
} |
| 132 |
int utcstr_to_mins(Str255 utc, long *out) { |
| 133 |
long minutes; |
| 134 |
if (!validate_utc(utc)) return false; |
| 135 |
minutes = 0; |
| 136 |
minutes += ((utc[2] - 0x30) * 10) * 60; |
| 137 |
minutes += (utc[3] - 0x30) * 60; |
| 138 |
minutes += (utc[5] - 0x30) * 10; |
| 139 |
minutes += (utc[6] - 0x30); |
| 140 |
if (utc[1] == '-') |
| 141 |
minutes = -minutes; |
| 142 |
if (out) *out = minutes; |
| 143 |
return true; |
| 144 |
} |
| 145 |
|
| 146 |
int TryMacNTP(Str255 ntp1, Str255 ntp2, Str255 utc) { |
| 147 |
struct ntp_packet payload; |
| 148 |
enum MacNTPError error; |
| 149 |
OSErr oserr = 0; |
| 150 |
minutes_t utc_offset; |
| 151 |
|
| 152 |
if (!utc[0]) return 1; |
| 153 |
if (!utcstr_to_mins(utc, &utc_offset)) return 1; |
| 154 |
|
| 155 |
if (!ntp1[0]) return 1; |
| 156 |
PtoCstr(ntp1); |
| 157 |
error = MacNTPFetchTime((char *)(ntp1), &payload, &oserr, utc_offset); |
| 158 |
// If the first lookup fails and we have a fallback, try it. |
| 159 |
if (error == DNSResolveFailed && ntp2[0]) { |
| 160 |
PtoCstr(ntp2); |
| 161 |
error = MacNTPFetchTime((char *)(ntp2), &payload, &oserr, utc_offset); |
| 162 |
} |
| 163 |
dump_error(error, oserr); |
| 164 |
if (error) return 1; |
| 165 |
|
| 166 |
error = MacNTPSetSystemTime(&payload, utc_offset); |
| 167 |
dump_error(error, oserr); |
| 168 |
if (error) return 1; |
| 169 |
return 0; |
| 170 |
} |
| 171 |
|
| 172 |
void main() { |
| 173 |
Ptr our_init_ptr; |
| 174 |
unsigned long systime; |
| 175 |
StringHandle ntp1; |
| 176 |
StringHandle ntp2; |
| 177 |
StringHandle utc; // Offset in minutes as pstr |
| 178 |
long answer; |
| 179 |
OSErr gestalt_err; |
| 180 |
asm { |
| 181 |
move.L A0, our_init_ptr; |
| 182 |
} |
| 183 |
RememberA0(); |
| 184 |
SetUpA4(); |
| 185 |
// This is already locked in the resource attrs |
| 186 |
g_mtcp_init_handle = RecoverHandle(our_init_ptr); |
| 187 |
|
| 188 |
// First we check if MacTCP is available |
| 189 |
// This also assumes Gestalt support |
| 190 |
gestalt_err = Gestalt(gestaltVersion, &answer); |
| 191 |
if (gestalt_err) { |
| 192 |
// Gestalt not available, it was introduced in 6.0.4 |
| 193 |
// And MacTCP in 6.0.3, so we're just going to assume |
| 194 |
// the user has at least 6.0.8 |
| 195 |
ShowInitIcon(MACNTP_ICN_ID_FAILURE, true); |
| 196 |
dump_error(GestaltMissing, 0); |
| 197 |
goto skip; |
| 198 |
} |
| 199 |
|
| 200 |
if (Gestalt('mtcp', nil)) { |
| 201 |
// MacTCP not found |
| 202 |
ShowInitIcon(MACNTP_ICN_ID_FAILURE, true); |
| 203 |
dump_error(MacTCPNotFound, 0); |
| 204 |
goto skip; |
| 205 |
} |
| 206 |
|
| 207 |
ntp1 = GetString(NTP1_STR_ID); |
| 208 |
ntp2 = GetString(NTP2_STR_ID); |
| 209 |
utc = GetString(UTC_STR_ID); |
| 210 |
if (!ntp1 || !ntp2 || !utc) { |
| 211 |
#ifdef DEBUGGING |
| 212 |
DebugStr("\pSTR rsrcs missing"); |
| 213 |
#endif |
| 214 |
ShowInitIcon(MACNTP_ICN_ID_FAILURE, true); |
| 215 |
goto skip; |
| 216 |
} |
| 217 |
/*if (!*ntp1[0] && !*ntp2[0]) { |
| 218 |
#ifdef DEBUGGING |
| 219 |
DebugStr("\pBoth NTP urls empty"); |
| 220 |
#endif |
| 221 |
ShowInitIcon(MACNTP_ICN_ID_FAILURE, true); |
| 222 |
goto skip; |
| 223 |
}*/ |
| 224 |
HLock(ntp1); |
| 225 |
HLock(ntp2); |
| 226 |
HLock(utc); |
| 227 |
|
| 228 |
//TODO: Move this up a bit |
| 229 |
#ifndef DEBUGGING |
| 230 |
// We only try to update the clock if we're well into the 20th century |
| 231 |
ReadDateTime((unsigned long *)&systime); |
| 232 |
if (systime > SECONDS_TO_02_09_1996) { |
| 233 |
ShowInitIcon(MACNTP_ICN_ID_SUCCESS, true); |
| 234 |
goto skip; |
| 235 |
} else { |
| 236 |
ShowInitIcon(MACNTP_ICN_ID_PENDING, false); |
| 237 |
} |
| 238 |
#else |
| 239 |
ShowInitIcon(MACNTP_ICN_ID_PENDING, false); |
| 240 |
#endif |
| 241 |
|
| 242 |
if (!TryMacNTP(*ntp1, *ntp2, *utc)) { |
| 243 |
ShowInitIcon(MACNTP_ICN_ID_SUCCESS, true); |
| 244 |
} else { |
| 245 |
ShowInitIcon(MACNTP_ICN_ID_FAILURE, true); |
| 246 |
} |
| 247 |
|
| 248 |
// I have no idea if unlock is needed here. Maybe not. |
| 249 |
HUnlock(ntp1); |
| 250 |
//DisposHandle(ntp1); |
| 251 |
HUnlock(ntp2); |
| 252 |
// For whatever reason, System 6 crashes on boot if we |
| 253 |
// try to dispose this specific handle. Okay. |
| 254 |
//DisposHandle(ntp2); |
| 255 |
HUnlock(utc); |
| 256 |
//DisposHandle(utc); |
| 257 |
HUnlock(g_mtcp_init_handle); |
| 258 |
skip: |
| 259 |
error: |
| 260 |
RestoreA4(); |
| 261 |
} |