/* TLLY ver=77 */ /* *=* Copyright 1996, 1997 WebTV Networks, Inc. All rights reserved. */ #ifndef FIJI /* * This is the "base" TellyScript Fragment. It is suppressed for FIJI * (see comments below). * * This has all of the generic stuff. Fragments for each ISP should be * appended to this one, along with the "PatternDial" function generated * by the server. * * --- * * v1.0 (e.g. build 105+) is version() 5 (phoneSettings len = 105) * v1.1 (e.g. build 253+) is version() 8 (phoneSettings len = 172) * v1.2 (e.g. build 430+) is version() 9 (phoneSettings len = 336) * v1.3 (e.g. build 1040+) is version() 9 (phoneSettings len = 336?) * v1.4 (e.g. build 1150+) is version() 11?? (phoneSettings len = 344) * v2.0 (e.g. build 2039+) is version() ? (phoneSettings len = ?) * v2.1 (e.g. build 3000+) is version() 11 (phoneSettings len = ?) * v2.2 (e.g. build 3300+) is version() 11? (phoneSettings len = ?) * * --- * * TellyScript is a lot like C, but there are a few things that the * interpreter doesn't know about: * const, volatile, static, extern * switch, case, default, goto, do, continue * long (use "int"), short, float, double, void * Return types on functions (they just return 4-byte "int") * Global "char blah[xx]" statements (they end up with a size of 4) * Initialized globals ("int foo = 0" ends up as 1??) * "if (0 && something-else)" will evaluate "something-else" * Typecasts (don't use "(char*)foo"... assume everything is 4 bytes here) * Bitwise '|', '&', and '^' * Some of the intrinsics are kinda lame: * printf() understands "%l" and "%d" but not "%ld" * sprintf() doesn't understand things like "%.31s" * sprintf(foostr, "%s", mystring) expands '%' chars in "mystring" (!!) * * Be sure to put braces around all parts of else-if clauses; in some * cases a "return" won't come all the way out if you don't. * * By convention, intrinsic function names are entirely lower case, and * script-defined function names are mixed. * * --- * * As of the "Funk" service, a limited form of pre-processing is done. We * allow: * * #ifdef CLASSIC * #ifndef CLASSIC * #else * #endif * #if 1 * #if 0 * * We silently remove: * #include ... * * All directives MUST be left-aligned. Nested #ifdefs are allowed. * Extra spaces or tab characters between the "#if" or "#ifdef" and the * label are NOT allowed, and "# ifdef" is NOT valid. * * It's okay to put comments on the lines, or write "#endif FOO". * * --- * * The Rockwell chipset appears to have a 64-character line buffer. Each * individual command (i.e. stuff that starts with "AT" and ends with "\r") * has to fit, or the end will be truncated. * * --- * * The dial settings are copied out of NVRAM. For 1.0 boxes this was * broken (it copied sizeof(struct), so 1.0 boot ROMs don't give you * access to more recent fields), but a workaround exists in the * GetPhoneSettings function. * * char* callWaitingPrefix = &settings[0]; * char* dialOutsidePrefix = &settings[32]; * char* accessNumber = settings[64]; * char usePulseDialing = settings[96]; * char audibleDialing = settings[97]; * char disableCallWaiting = settings[98]; * char dialOutsideLine = settings[99]; * char changedCity = settings[100]; * char waitForTone = settings[101]; * char hasCallWaiting = settings[102]; \* never used? *\ * char useCallWaitingHack = settings[103]; * char dialSpeed = settings[104]; * char cwSensitivity = settings[105]; \* version >= 7 *\ * char brokenPBX = settings[106]; \* version >= 8 *\ * char access800 = settings[108]; \* version >= 8 *\ * char* dialLDPrefix = &settings[140]; \* version >= 8 *\ * char* openISPPhoneNumber = &settings[172]; \* version >= 9 *\ * char* openISPPassword = &settings[204]; \* version >= 9 *\ * char* openISPUserName = &settings[236]; \* version >= 9 *\ * char* openISPProviderName = &settings[268]; \* version >= 9 *\ * char openISPOn = settings[300]; \* version >= 9 *\ * char* openISPPhoneNumber2 = &settings[304]; \* version >= 9 *\ * char* featureFlags = &settings[336]; \* version >= 11 *\ * char* maxFeatureFlag = &settings[340]l; \* version >= 11 *\ * * --- * The fiji client is short of NVRAM. Accordingly, we have preloaded all of * ver 65 of base.tsf. All of the functionality has been replaced by * preloaded functions, which are somewhat like intrinsics, but expressed * in tellyscript, rather than C. They can thus call back into downloaded * functions. * * Any calling in either direction between preloaded functions and * downloaded functions requires that the function names not be abbreviated. * However, we don't want to lose the benefits of name abbreviation for * non-Fiji clients. We accomplish this as follows: * - functions which are called by generated code retain their names and * so they are added to the list of identifiers immune to abbreviation. * (Compression will mitigate the effects of this somewhat). * - functions not in the above list, and which may be downloaded to be * called by preloaded functions have names which start with an underscore, * and we suppress abbreviation for such names. * * We use this to allow any function to be overridden, as follows. * * Each of the preloaded functions calls, as its first action, a hook function. * The name of the hook function is that of the original function, but with * an underscore prefix. If the hook function is unimplemented (or returns the * "unimplemented" value 0x42554646) the preloaded function will return * immediately. This allows any or all of the functions to be overridden or * supplemented by downloaded code. * * As a consequence of all of this, all of this file is simply suppressed * for Fiji. For efficiency, this is done by one mondo #ifndef. (We're inside * it right now. * * Bear in mind that if you make a change it won't automatically happen for Fiji * clients. If you want to make a change for Fiji, you may have to add to the * list of names for which abbreviation is suppressed. * * 1999/09/10 -- rule of thumb: anything added to base.tsf that is referenced * by generated code MUST be included for FIJI as well. If you add a couple * of globals that are set up by InitGeneratedValues, they MUST be made * visible to FIJI. */ /* * Globals. Don't count on these being initialized to zero, and don't give * them static initializers. */ int gDTERate; /* connection stats, set by ParseResult() */ int gDCERate; int gProtocol; int gCompression; int gConnected; /* set to 1 by ParseResult if we're considered connected */ int gUsingOpenISP; /* are we an OpenISP script? */ int gNVRAMMayBeInvalid; /* was the script sent down for a brain-dead download?*/ char* gCHAPSecret; /* box's CHAP secret, embedded in script by the service */ char* gUsername; /* box's username for login, embedded in script by the * service */ char* gPAPPassword; /* box's PAP password, embedded in script by the * service */ int gEnable56K; /* if set, allow 56K connections; if not, don't */ int gDisguiseRate; /* if set, reverse the DTE and DCE rates */ int gFlexKnob=5; /* 56k knob for Rockwell, 5=most aggressive (default) */ #endif /*!FIJI*/ /* FIJI doesn't have these built-in */ int gBlock911=1; /* This flag is now set for all services. */ int gWantsVideoAd; /* if set, user wants to download Video Ads */ /* This flag is not needed anymore, left here for future use */ #ifndef FIJI /* * =========================================================================== * Misc utility functions (some of which are now intrinsics) * =========================================================================== */ /* * WinkDTR - bounce DTR down then up, to get the modem into a happy state. */ WinkDTR() { setdtr(0); delay(30); setdtr(1); delay(30); return 0; } /* * StrLen - there's no strlen() intrinsic (in versions < 7). */ StrLen(char* str) { int len = 0; while (*str++) len++; return len; } /* * StrnCpy - ain't no strncpy() either (at least not for versions < 7). */ StrnCpy(char* dstStr, char* srcStr, int count) { while (*srcStr) { if (!count--) break; *dstStr++ = *srcStr++; } if (count > 0) /* if src finished first, null-terminate the target */ *dstStr = 0; return 0; /* for ANSI this would return dstStr */ } /* * 018647 * PatternCmp - compare a pattern to two supplied strings.. this is particularly * useful for detecting 911 patterns. */ PatternCmp(char* pattern, char* prefix, char* number) { char *oldpattern = pattern; char *oldprefix = prefix; char *oldnumber = number; while(*pattern){ if(*prefix){ if(*prefix != ',') { if(*pattern != *prefix) { return 0;} prefix++; } else { if(*number) { if(*pattern != *number) { pattern = oldpattern; number = oldnumber; while(*pattern){ if(*pattern != *number) {return 0;} number++;pattern++; }return 1; } prefix++; number++; } else { return 0;} } }else if(*number) { if(*pattern != *number) { return 0;} number++; } else { return 0;} pattern++; } return 1; } /* * OStrChr - like strchr(), but returns an offset instead of a pointer. * Returns -1 if not found. * * This does NOT treat the '\0' as part of the string. Sorry. * * Note the real strchr() intrinsic was added in version 8 or 9. */ OStrChr(char* s, int c) { char* orig = s; while (*s != '\0') { if (*s == c) return s - orig; s++; } return -1; } /* * Test to see if the idx'th bit in "flags" is set. Bit 0 is the LSB. * (The interpreter doesn't have bitwise operations.) */ BitTest(int flags, int idx) { while (idx > 0) { flags = flags / 2; idx--; } return flags % 2; } /* * SetProgress - shorthand notation for three intrinsic calls: * * setprogresstext(message) * setprogresspercentage(percent) * setprogressdirty(1) */ SetProgress(char* message, int percent) { setprogresstext(message); setprogresspercentage(percent); setprogressdirty(1); return 0; } /* * GetPhoneSettings - hairy replacement for getphonesettings(). The values * pointed to by this should ONLY be used for 1.1 and later options. For * access to options present in client 1.0, you MUST use getphonesettings() * instead. (Like C, TellyScripts are case-sensitive.) * * Get the phone settings. For 1.1 and later clients, we can just use * the getphonesettings() intrinsic. For 1.0 clients, getphonesettings() * copied sizeof(struct) rather than everything that was there, so 1.0 * boxes can't see anything added after 1.0. (This wouldn't be a big * problem, except that the boot ROM is effectively a 1.0 client.) * * We work around the problem by finding the phone options in NVRAM. The * trick is to avoid doing this when NVRAM is invalid (such as when we're * doing a brain-dead flash download), and also to figure out what version * of the client wrote the phone settings. * * As an added obstacle, editing the phone options on a box only changes * the copy in RAM. If they've made changes, but haven't rebooted, we * need to use the copy in RAM instead of the copy in NVRAM. As a general * rule, we only use the NVRAM copy if the version that wrote the phone * settings is different from the current version. I'm not *quite* sure * how things look right after an upgrade, when the box converts the old * options to the new format, initializing fields and whatever else. We * might end up looking at older data on the first boot. We can fix this * for the upgrade case by checking to see if the settings are *older* * than the current rev, but the downgrade case looks just like a flash dl. * * But wait, there's more. A user doing a flash download could use the * manual configuration options to specify an access number. If we're * looking at NVRAM, we can't see it. The solution is to always use the * getphonesettings() pointer for 1.0 options, and use the GetPhoneSettings * pointer for 1.1 and later options. (Since the boot ROM is 1.0, by * definition it can't see the later options, so there's no risk of them * being changed.) * * We have to be careful that we don't go wandering through NVRAM on a * brain-dead box. Since we never give an OpenISP script to zombies, * we can freely do this in conjunction with OpenISP, but have to be * very careful otherwise. Be sure gNVRAMMayBeInvalid is initialized * correctly before calling this. * * Returns 0 on error. */ GetPhoneSettings(int* pSettingsVersion) { int offset; int *len_ptr; int *tag; int *rom_size = 0xbf00000c; int *rom_end; *pSettingsVersion = 0; /* * If we're new enough, or we're brain-dead, return our own version * and just use the intrinsic function. */ if (version() >= 9 || gNVRAMMayBeInvalid) { *pSettingsVersion = version(); return getphonesettings(); } /* use the Code de Rubin to find the 'FONE' chunk */ if (*rom_size == 0x80000) { /* two mb ROM */ offset = 0xbf1fc000; rom_end = 0xbf200000; } else if (*rom_size == 0x100000) { /* four mb ROM */ offset = 0xbf3fc000; rom_end = 0xbf400000; } else { printf("TS: couldn't get size of ROM"); return 0; } offset = offset + 16; /* skip past NVRAM header */ while( (offset + 9) < rom_end) { len_ptr = offset; tag = offset + 4; if (*tag == 0x464f4e45) { if (*len_ptr == 105) *pSettingsVersion = 5; else if (*len_ptr == 172) *pSettingsVersion = 8; else if (*len_ptr == 336) *pSettingsVersion = 9; else if (*len_ptr == 344) *pSettingsVersion = 11; else *pSettingsVersion = version(); /* punt... this is BAD */ printf("TS: FONE@%x, len=%d, vers=%d", offset+8, *len_ptr, *pSettingsVersion); /* if the versions match, return the RAM copy */ if (*pSettingsVersion == version()) { return getphonesettings(); } else { return offset + 8; } } offset = offset + *len_ptr + 8; offset = offset - (offset % 4); } /* * We didn't find it in NVRAM. The only way this should be possible * is if they just did a 32768, and the in-RAM copy of the settings * hasn't been flushed out yet. */ *pSettingsVersion = version(); return getphonesettings(); } /* * =========================================================================== * Initializing * =========================================================================== */ #ifndef CLASSIC /* * GetHardwareInfo - figure out some things about the platform we're * running on. * * "*pIsEarlyK56" is set if we're using Rockwell's v1.0 K56 firmware. * "*pAcceptsS220" is set if the modem recognizes S220 as a valid S-register. * * Assumes that the modem is happy and in V1E0 (verbose result code, no * echo) mode. * * NOTE: we don't really check for and try to handle the case where the * modem goes out to lunch and we're hitting the getline() timeouts. Since * we've already talked to the modem a little before getting here, I think * it's safe to assume that we won't fail here and then succeed later. If * the information we get from this function is more critical than what we * have now, we should be more careful. * * Returns 0 if all went well, nonzero on error. */ GetHardwareInfo(int* pIsEarlyK56, int* pAcceptsS220, int* pIsRockwell, int* pAcceptsAmpP, int* pAcceptsPlusMR) { char resultBuf[64]; /* look for v1.0 Rockwell K56 firmware... if found, limit to <= V.34 */ flush(); sendstr("ATI3\r"); /* modem firmware query */ printf("TS: SENT ATI3"); getline(resultBuf, 63, 300); /* throw out the first one */ if (!getline(resultBuf, 63, 300)) /* keep 2nd; note it has leading '\r' */ return 1; printf("TS: fw query got '%s'", resultBuf+1); setfirmwareversion(resultBuf+1); resultBuf[21] = '\0'; /* me falta strncmp in 1.0 */ *pIsEarlyK56 = 0; if (strcmp(resultBuf+1, "V1.001_WEBTV-K56_DLP") == 0) *pIsEarlyK56 = 1; /* * Softmodem can't handle AT+MS, so try to detect Rockwell. Sadly, * there doesn't seem to be a simple way to do this. */ *pIsRockwell = 0; if (strcmp(resultBuf+11, "TV-K56_DLP") == 0) *pIsRockwell = 1; resultBuf[15] = '\0'; /* do we have strncmp in 2.0? */ if (strcmp(resultBuf+8, "K56_DLP") == 0) /* V2.200-K56_DLP_RAM */ *pIsRockwell = 1; printf("TS: isRockwell=%d", *pIsRockwell); /* see if it's a Rockwell that supports +MR */ *pAcceptsPlusMR = 0; if (*pIsRockwell) { flush(); sendstr("AT+MR=0\r"); getline(resultBuf, 63, 300); /* throw out 1st */ if (!getline(resultBuf, 63, 300)) /* keep 2nd */ return 1; printf("TS: +MR=0 test returned '%s'", resultBuf+1); if (strcmp(resultBuf+1, "OK") == 0) *pAcceptsPlusMR = 1; } /* see if it will take an S220 command */ flush(); sendstr("ATS220=0\r"); /* JP CW --> extremely insensitive */ printf("TS: SENT ATS220=0"); getline(resultBuf, 63, 300); /* throw out 1st */ if (!getline(resultBuf, 63, 300)) /* keep 2nd */ return 1; printf("TS: S220 test returned '%s'", resultBuf+1); *pAcceptsS220 = 0; if (strcmp(resultBuf+1, "OK") == 0) *pAcceptsS220 = 1; /* see if it will take an &Pn command -- desktop Supras won't */ flush(); sendstr("AT&P0\r"); printf("TS: SENT AT&P0"); getline(resultBuf, 63, 300); /* throw out 1st */ if (!getline(resultBuf, 63, 300)) /* keep 2nd */ return 1; printf("TS: AT&P0 test returned '%s'", resultBuf+1); *pAcceptsAmpP = 0; if (strcmp(resultBuf+1, "OK") == 0) *pAcceptsAmpP = 1; return 0; } #endif /* * Initialize - set up the modem configuration and dialing commands, and * set some options to defaults. Does not talk to the modem. * * Some of this assumes a Rockwell modem, some is generic. * * "staticConfigBuf" will be filled in with the commands that never * change. "dynamicConfigBuf" gets the commands that are based on the * user settings. The split is largely historical, but serves as a * convenient way to keep the total length of each command under 64 chars. */ Initialize(char* staticConfigBuf, char* dynamicConfigBuf) { int settingsVersion; char *settings = getphonesettings(); char *extendedSettings = GetPhoneSettings(&settingsVersion); char audibleDialing = settings[97]; char waitForTone = settings[101]; char useCallWaitingHack = settings[103]; char dialSpeed = settings[104]; char cwSensitivity = extendedSettings[105]; /* version >= 7 */ char *cwValue = "14"; #ifndef CLASSIC char *s220Value; int isEarlyK56, acceptsS220, isRockwell, acceptsAmpP, acceptsPlusMR; char *flexKnobStr; /* sairam testing */ #endif /* * Talk to the modem. */ enablemodem(); /* turn on ints */ setflowcontrol(3); /* hardware flow */ setbaud(57600); /* meaningless except for Mac simulator */ /* check to see if the modem is on speaking terms */ if (SendAndWaitForOK("AT&D2V1E0\r", "OK")) return 3; /* kTellyConfigurationError */ #ifndef CLASSIC /* figure out what we got? */ if (GetHardwareInfo(&isEarlyK56, &acceptsS220, &isRockwell, &acceptsAmpP, &acceptsPlusMR)) return 3; /* kTellyConfigurationError */ if (isEarlyK56) printf("TS: found v1.0 K56 fw, disabling 56K"); strcpy(staticConfigBuf, "AT"); if (isRockwell) { if (isEarlyK56 || !gEnable56K) { strcat(staticConfigBuf, "+MS=11,1"); /* disable 56K */ } /* else { /+ Rockwell 56k Aggressiveness knob - sairam testing +/ if (isRockwell) { sprintf(staticConfigBuf+2, "!0049=0%d", gFlexKnob); printf("TS: setting 56k knob to %d", gFlexKnob); } } */ } else { /* assume softmodem */ if (!gEnable56K) { strcat(staticConfigBuf, "S51=31"); /* disable 56K */ } } if (acceptsPlusMR) /* suppress extended result codes we don't parse */ strcat(staticConfigBuf, "+MR=0"); #else strcpy(staticConfigBuf, "AT"); /* CLASSIC doesn't support 56K */ #endif /* * Modem configuration. This sets things so we connect with V.34 only, * and at 14.4Kbps or better. (970129: I've removed the +MS command * in favor of handling it explicitly, because it takes a full minute * for the modem to drop, and it gets blended into the "no carrier" * stats.) * * We set V1 here and V0 later because the modem can be a little groggy * after a reset, and returns OK even though V0 was set. * * If we were still doing this, "+MS=11,1,14400" is the correct string. * The "demo" service should use "+MS=11,1,9600" instead. * * The "\N2" forces &Q5, but I'm leaving &Q5 in so that if we remove * the \N2 later we won't forget to put the &Q5 back in. [ Q5 has * returned while we evaluate the #of disconnects ] */ strcat(staticConfigBuf, "S38=0S30=180S95=36&D2V1E0L3&Q5&K3\r"); /* set default dialing string */ strcpy(dynamicConfigBuf, "ATV0"); /* handle audible dialing */ /* check that connectingwithvideoad() returns 1, because * 0 or 0x42554646 mean false (the big number is from unimplemented) */ if (audibleDialing && (connectingwithvideoad() != 1)) strcat(dynamicConfigBuf, "M1"); else strcat(dynamicConfigBuf, "M0"); /* add blind dialing */ if (waitForTone) strcat(dynamicConfigBuf, "S6=10X4"); else strcat(dynamicConfigBuf, "S6=4X3"); /* handle call waiting hack; default value of "14" means "off" */ if (settingsVersion >= 7) { if (useCallWaitingHack) { if (cwSensitivity == 1) /* most likely to hang up */ cwValue = "113"; else if (cwSensitivity == 2) cwValue = "116"; else if (cwSensitivity == 3) cwValue = "88"; else if (cwSensitivity == 4) /* most likely to ignore calls */ cwValue = "92"; else cwValue = "92"; } } else { if (useCallWaitingHack) cwValue = "92"; } printf("TS: vers=%d/%d, cw-hack=%d, cw-sens=%d, setting S10=%s", version(), settingsVersion, useCallWaitingHack, cwSensitivity, cwValue); strcat(dynamicConfigBuf, "S10="); strcat(dynamicConfigBuf, cwValue); #ifndef CLASSIC /* set the JP "catch-phone" sensitivity; this is *in addition* to S10 */ if (acceptsS220) { if (cwSensitivity == 1) /* most likely to hang up */ s220Value = "32"; else if (cwSensitivity == 2) s220Value = "21"; else if (cwSensitivity == 3) s220Value = "11"; else /* == 4 or bogus */ /* most likely to ignore calls */ s220Value = "1"; printf("TS: setting S220=%s", s220Value); strcat(dynamicConfigBuf, "S220="); strcat(dynamicConfigBuf, s220Value); } else { printf("TS: not setting S220"); /* CLASSIC boxes; others? */ } #endif /* * Set the dial speed. */ if (dialSpeed == 0) { strcat(dynamicConfigBuf, "S11=200"); /* slow */ } else if (dialSpeed == 1) { strcat(dynamicConfigBuf, "S11=110"); /* medium */ } else if (dialSpeed == 2) { strcat(dynamicConfigBuf, "S11=60"); /* fast */ } else if (dialSpeed == 3) { strcat(dynamicConfigBuf, "S11=1"); /* blazing (not in UI) */ } #ifndef CLASSIC /* * Set the pulse speed. &P0 is default for US, but we use JP pulse * values for now since they seem to work anyway. Some desktop Supra * modems don't understand "&Pn", and will fail to configure, so we * use the "acceptsAmpP" auto-detect value. */ if (acceptsAmpP) { if (dialSpeed == 0 || dialSpeed == 1) { strcat(dynamicConfigBuf, "&P1"); /* 10PPS 33% */ } else if (dialSpeed == 2 || dialSpeed == 3) { strcat(dynamicConfigBuf, "&P3"); /* 20PPS 33% */ } } #endif /* put a carriage return on the end */ strcat(dynamicConfigBuf, "\r"); /* (this is now generated) */ /*setnameservice(0xcf4cb483, 0xce439805);*/ /* * Set the default window size. It should already be this, but there's * little harm in being paranoid. */ setwindowsize(7); return 0; } /* * GetDialingPrefix - figure out what needs to be in the dialing prefix. * * The string will be a combination of the call waiting prefix and the * outside line prefix. If we can't find anything to put in the string * (i.e. no prefixes have been specified), the buffer will be set empty * (i.e. start with '\0'). * * This function doesn't talk to the modem; it just builds the string. * * "prefixBuffer" must be able to hold the cw prefix (31 bytes), the * outside line prefix (16 bytes), and a terminating null. Total 48 bytes. */ GetDialingPrefix(char* prefixBuffer, int isLocal) { int settingsVersion; char *settings = getphonesettings(); char *extendedSettings = GetPhoneSettings(&settingsVersion); char *callWaitingPrefix = &settings[0]; char *dialOutsidePrefix = &settings[32]; char disableCallWaiting = settings[98]; char dialOutsideLine = settings[99]; char *dialLDPrefix = &extendedSettings[140]; /* version >= 8 */ prefixBuffer[0] = '\0'; /* handle a special char sequence to disable call waiting */ if (disableCallWaiting) strcat(prefixBuffer, callWaitingPrefix); /* use LD prefix or normal dial-outside-line prefix as appropriate */ if (settingsVersion >= 8) { if (dialLDPrefix[0] && !isLocal) { printf("TS: Using LD prefix"); strcat(prefixBuffer, dialLDPrefix); strcat(prefixBuffer, ","); } else { if (dialOutsideLine) { strcat(prefixBuffer, dialOutsidePrefix); strcat(prefixBuffer, ","); } } } else { if (dialOutsideLine) { strcat(prefixBuffer, dialOutsidePrefix); strcat(prefixBuffer, ","); } } return 0; } /* * SendAndWaitForOK - send a string to the modem and wait for the "OK" * response. * * Retries "retryCount" times, waiting for two seconds and bouncing DTR * down and up between each. * * Returns 0 on success, nonzero on failure. */ SendAndWaitForOK(char* str, char* okstr) { int retries = 0; while (retries++ < 4) { flush(); /* flush data that's pending *from* the modem */ sendstr(str); if (waitfor(okstr, StrLen(okstr), 120)) { printf("TS: SENT config str '%s'", str); break; } else { printf("TS: TIMEOUT waiting for OK (str='%s')", str); WinkDTR(); } } if (retries >= 4 ) { printf("TS: Couldn't get '%s' from modem", okstr); setdtr(0); return 3; } return 0; } /* * InitModem - initialize the modem with "staticConfig" and "dynamicConfig". * We do this before each time we dial. * * IMPORTANT: there is an assumption we're making that dropping DTR doesn't * erase the stuff we've already sent (i.e. by reinitializing the modem). * If it does, retries for the second string will undo the first string. * * Returns 0 on success. Returns nonzero and drops DTR on failure. */ InitModem(char* staticConfig, char* dynamicConfig) { setstatus(6); /* kTellyInitializingModem */ SP_PreparingToCall(13); WinkDTR(); if (SendAndWaitForOK(staticConfig, "OK")) { setdtr(0); return 3; /* kTellyConfigurationError */ } /* this assumes "dynamicConfig" enables numeric result codes */ if (SendAndWaitForOK(dynamicConfig, "0")) { setdtr(0); return 3; /* kTellyConfigurationError */ } /* turn off a bad idea */ /*printf("TS: Overriding exclusion circuit...");*/ setforcehook(1); return(0); } /* * =========================================================================== * Dialing * =========================================================================== */ /* * ParseResult - parse the return code from the modem. * * This is expected to work on Rockwell-based modems only. Other modems * should implement the modem_parseresult intrinsic. */ ParseResult(int result) { int retcode; char* comment = ""; #ifndef CLASSIC /* no CLASSIC box supports modem_parsersult() */ retcode = modem_parseresult(&result, &gDTERate, &gDCERate, &gCompression, &gProtocol, &gConnected, &comment); if (retcode != 0x42554646) { printf("TS: parseresult -- %d %s (retcode=%d)", result, comment, retcode); if (gDCERate && gDCERate < 14400) { retcode = 14; /* kTellyVerySlowConnect */ } return retcode; } #endif retcode = 0; if (result == 0) /* OK */ { comment = " OK"; retcode = 1; /* okay */ } else if (result == 3) /* NO CARRIER */ { comment = "NO CARRIER"; retcode = 12; /* kTellyNoCarrier */ } else if (result == 6) /* NO DIALTONE */ { comment = "NO DIALTONE"; retcode = 5; /* kTellyNoDialtone */ } else if (result == 7) /* BUSY */ { comment = "BUSY"; retcode = 7; /* kTellyBusy */ } else if (result == 8) /* NO ANSWER */ { comment = "NO ANSWER"; retcode = 6; /* kTellyNoAnswer */ } else if (result >= 18 && result <= 19) /* CONNECT 57600 and 115200 */ { comment = "Connected!"; gConnected = 1; gDTERate = 57600 * (result - 17); } else if (result == 20) /* CONNECT 230400 */ { comment = "Connected!"; gConnected = 1; gDTERate = 230400; } else if (result >= 47 && result <= 51) /* CARRIER 2400 - CARRIER 12000 */ { gDCERate = 2400 * (result - 46); retcode = 14; /* kTellyVerySlowConnect */ } else if (result >= 52 && result <= 58) /* CARRIER 14400 - CARRIER 28800 */ { gDCERate = 2400 * (result - 46); } else if (result == 66) /* COMPRESSION: CLASS 5 */ { gCompression = 1; } else if (result == 67) /* COMPRESSION: V.42bis */ { gCompression = 2; } else if (result == 69) /* COMPRESSION: NONE */ { gCompression = 0; } else if (result == 76) /* PROTOCOL: NONE */ { gProtocol = 0; } else if (result == 77) /* PROTOCOL: LAPM */ { gProtocol = 1; } else if (result == 78 || result == 60) /* CARRIER 31200 */ { gDCERate = 31200; } else if (result == 79 || result == 65) /* CARRIER 33600 */ { gDCERate = 33600; } else if (result == 80) /* PROTOCOL: ALT */ { gProtocol = 2; } else if (result == 81) /* PROTOCOL: ALT-CELLULAR */ { gProtocol = 3; } else if (result >= 150 && result <= 162) /* CARRIER 32000 - 56000 (K56) */ { gDCERate = 2000 * (result - 134); } else { printf("TS: ParseResult -- %d unknown", result); retcode = 9; /* kTellyUnknown */ } printf("TS: ParseResult -- %d %s (retcode=%d)", result, comment, retcode); return retcode; } /* * DialModem - dial "numberToDial" after first sending some optional * dial prefixes. * * "isLocal" is needed to make dialLDPrefix work right. * * NOTE: the UI restricts the fields as follows: * dial prefix 16 bytes (32-char buffer) * cw prefix 31 bytes (32-char buffer) * access number 31 bytes (32-char buffer) * * Returns 0 on success. */ DialModem(char* numberToDial, int isLocal) { int settingsVersion; char *settings = getphonesettings(); char *extendedSettings = GetPhoneSettings(&settingsVersion); char *accessNumber = &settings[64]; char usePulseDialing = settings[96]; char brokenPBX = extendedSettings[106]; /* version >= 8 */ int isTollFree, dollarOffset; char prefixBuffer[64]; /* see GetDialingPrefix for size recommendation */ char* prefixStr; /* points into prefixBuffer */ char* afterDollar; char buffer[32]; /* * Set the working number. This is used by the phone logs and by * the "☎" tag. We want to grab at most 32 chars (64 for * version >= 9), or setworkingnumber() blows up. * * (We hijack "buffer" for a little while.) */ if (gUsingOpenISP) { strcpy(buffer, "ISP-"); StrnCpy(buffer+4, numberToDial, 31-4); } else { StrnCpy(buffer, numberToDial, 31); } buffer[31] = 0; setworkingnumber(buffer); /* is it toll free in this locale (needed for "visible dialing")? */ isTollFree = IsTollFree(numberToDial); /* figure out if this is one of those fancy "dollar dialing" things */ dollarOffset = OStrChr(accessNumber, '$'); if (dollarOffset >= 0) { /* copy accessNumber, and split it at the '$' */ strcpy(buffer, accessNumber); afterDollar = buffer + dollarOffset +1; buffer[dollarOffset] = '\0'; } /* strip off the leading digit under certain circumstances */ if (settingsVersion >= 8 && brokenPBX && (!accessNumber[0] || dollarOffset >= 0)) { if (DoStripLeadingDigit(numberToDial)) { /*printf("TS: BrokenPBX set, stripping digit");*/ numberToDial++; } } /* * Figure out what the dialing prefix should be. This is a * combination of the "9," dialing prefix field and the "call * waiting disable" prefix field. We also sneak the tone/pulse * dial command into the front of the buffer. */ if (usePulseDialing) strcpy(prefixBuffer, "ATDP"); else strcpy(prefixBuffer, "ATDT"); prefixStr = prefixBuffer + 4; GetDialingPrefix(prefixStr, isLocal); /* do "visible dialing" for version>=8 if it's not toll-free */ /*setstatus(3); * kTellyDialing */ setstatus(7); /* kTellyHandshake */ if (gUsingOpenISP) { /* always show for OpenISP */ SP_DialingNumber(prefixStr, numberToDial, 26); } else if (version() >= 8 /*&& audibleDialing*/) { if (accessNumber[0] && dollarOffset < 0) { SP_DialingAccessNumber(prefixStr, numberToDial, 26); } else if (isTollFree) { SP_DialingWebTV(26); } else { /* since this isn't an accessNumber, numberToDial should be short */ SP_DialingNumber(prefixStr, numberToDial, 26); } } else { SP_DialingWebTV(26); } /* * Send the commands to the modem. * * Someday we may want to split "prefixBuffer" and "numberToDial" * so that we detect dialtone after the prefix. This would help * avoid inadvertent 911 calls. */ /* 018647 * Make sure that no 911 pattern is dialled, combination of prefix and number! */ if (1) { if (PatternCmp("911", prefixStr, numberToDial)) { /* 911 pattern.. inform user!*/ alert("Error 911: Your receiver cannot connect to WebTV. Please contact Customer Care at 1-800-469-3288.", "", 0); } } if ((numberToDial[0] != 'A') && (numberToDial[0] != 'a')) { sendstr(prefixBuffer); printf("TS: SENT prefix '%s'", prefixBuffer); } if (dollarOffset < 0) { sendstr(numberToDial); printf("TS: SENT number '%s'", numberToDial); } else { sendstr(buffer); sendstr(numberToDial); sendstr(afterDollar); printf("TS: SENT fancy '%s' '%s' '%s'", buffer, numberToDial, afterDollar); } /* return to command mode after dialing */ sendstr(";\r"); flush(); /* this flushes data *from* the modem... needed here? */ return 0; } /* * WaitForConnect - watch the modem result codes until we're connected. * * Returns 0 on success. Returns nonzero and drops DTR on failure. */ WaitForConnect() { char *settings = getphonesettings(); char audibleDialing = settings[97]; char buffer[32]; int i, result, count; /* if we fail early, don't report the previous stats */ setconnectionstats(0, 0, 0, 0); /* * Loop until we're connected. We'll usually get four results, in this * order (but not always!): * 1: OK (dial string was accepted) * 2: DCE rate (modem speed) * 3: Compression and err correction (usually v.42bis, which implies v.42) * 4: DTE rate (serial port speed) */ i = 0; gConnected = 0; while (!gConnected && i < 6) { count = getline(buffer, 31, 4200); i++; if (count == 0) { printf("TS: TIMEOUT waiting for modem result"); setdtr(0); if (i == 1) return 4; /* kTellyDialingError */ else return 8; /* kTellyHandshakeFailure */ } result = ParseResult(atoi(buffer)); if (result == 1) { /* OK */ if (i == 1) { sendstr("ATD\r"); if (!audibleDialing) { /* keep it on screen long enough to see it */ delay(180); } /*setstatus(7); * kTellyHandshake */ SP_WaitingToConnect(39); result = 0; } else { /* probably garbage from modem that atoi() converted to zero */ setdtr(0); printf("TS: got odd '%s'", buffer); return 8; /* kTellyHandshakeFailure */ } } if (result != 0) { /* first one handles the NO ANSWER, NO DIALTONE, etc. */ setdtr(0); if (result == 12 && i > 2) { /* NO CARRIER after first result becomes "handshake failure" */ return 8; /* kTellyHandshakeFailure */ } return result; } if (i == 2) { setstatus(2); /* kTellyCarrier */ if (gUsingOpenISP) SP_ISPAnswering(52); else SP_WebTVAnswering(52); } } printf("TS: dterate=%d, dcerate=%d, prot=%d, comp=%d", gDTERate, gDCERate, gProtocol, gCompression); setconnectionstats(gDTERate, gDCERate, gProtocol, gCompression); return 0; } /* * DialIAP - init the modem, dial the IAP, and report errors as appropriate. * * Returns 0 on success, 1 on "roll over" failure, 2 on "stop now" failure. * The actual failure code is placed into "*pResult". * * *pResult will always be set nonzero when the function returns nonzero. */ DialIAP(char* staticConfig, char* dynamicConfig, char* iapName, char* number, int isLocal, int* pResult) { *pResult = InitModem(staticConfig, dynamicConfig); if (*pResult != 0) return 2; /* couldn't init, bail now */ /* reset globals before each attempt */ gDTERate = gDCERate = gProtocol = gCompression = 0; printf("TS: Calling %s/%s", iapName, number); DialModem(number, isLocal); *pResult = WaitForConnect(); if (*pResult) printf("TS: dialing failure, result=%d", *pResult); if (*pResult == 4 || *pResult == 5) { /* kTellyDialingError or kTellyNoDialtone, bail now */ return 2; /* bail now */ } else if (*pResult) { /* some other kind of failure; want to try next POP in line */ return 1; /* bail later */ } return 0; /* success! */ } /* * =========================================================================== * Chatting * =========================================================================== */ /* * [ ScriptedChat has been excised to reclaim space ] */ /* * CHPAPCHat - common CHAP/PAP chat function. * * Call setpapmode(1) or setpapmode(2), then call this with the fully-formed * user name and password. */ CHPAPChat(char* username, char* password) { setusername(username); setpassword(password); setstatus(5); /* kNegotiatingPPP */ SP_Connecting(88); if (!startppp()) { setdtr(0); if (getpppresult() == 3) { /* authentication failure */ printf("TS: PAP/CHAP auth failure"); return 10; } printf("TS: PPP negotiation failed"); return 11; } printf("TS: Connected!"); /*printf("TS: DEBUG: %d bytes available on stack", stack());*/ setstatus(1); /* kTellyConnected */ /* * Check free space remaining. The one place we really need this -- 1.0 * boxes -- is the one place that this trick won't work. :-( */ if (stack() < 512) { printf("WARNING: TS stack low"); SP_StackLow(100); } else if (gUsingOpenISP) SP_ConnectedToISP(100); else SP_ConnectedToWebTV(100); return 0; } /* * PAPChat - do a PAP-based login. * * Pass in a format string to use for the username. The result from * getserialnumber() will be placed into the first "%s" in the format. * If "fixedPassword" is nonzero, it will be used, otherwise the FCS * of the serial number is sent. * * Examples: * WebTV 800 : PAPChat("wtv_%s", 0); * UUNET : PAPChat("WTV/%s", 0); * CNC : PAPChat("%s!webtv", 0); * CNC (old) : PAPChat("artemis1", "webtv!96"); * * Returns 0 on success. Returns nonzero and drops DTR on failure. */ PAPChat(char* userNameBase, char* fixedPassword) { char username[48]; char password[48]; sprintf(username, userNameBase, gUsername); if (fixedPassword == 0) { sprintf(password, "%s", gPAPPassword); } else if (fixedPassword == 1) { /* pad out to 8 chars for PSI */ sprintf(password, "%08d", computefcs(gUsername)); } else if (fixedPassword == 2) { sprintf(password, "%d", computefcs(gUsername)); } else { strcpy(password, fixedPassword); } setpapmode(1); printf("TS: Using PAP with '%s'/'%s'", username, password); return CHPAPChat(username, password); } /* * CHAPSupported - does the box support CHAP right now? * * Returns a boolean value, set to "true" if the box supports CHAP. */ CHAPSupported() { int flags; flags = system_getboxfeatureflags(); if (flags == 0x42554646) return 0; /* don't support feature flags, must not support CHAP */ /*printf("BoxFeatureFlags = %d", flags);*/ /* feature flag 0 (0x0001) is kBoxHasChap */ if (BitTest(flags, 0)) return 1; else return 0; } /* * CHAPChat - do a CHAP-based login. * * For now, just use the user name and password passed in, without further * processing. */ CHAPChat(char* userNameBase, char* fixedPassword) { char username[48]; char password[48]; /* are we *sure* we support CHAP? */ if (!CHAPSupported()) { printf("TS: CHAP not supported!"); return 1; } sprintf(username, userNameBase, gUsername); if (fixedPassword == 0) strcpy(password, gCHAPSecret); else strcpy(password, fixedPassword); setpapmode(2); printf("TS: Using CHAP with '%s'/'%s'", username, password); return CHPAPChat(username, password); } /* * =========================================================================== * Command & Control * =========================================================================== */ /* * WhatTimeIsIt - figure out what time it is. * * The fancy "poptimized" scripts can use different POP lists based on * the month, day of the week, or hour of the day (useful for peak vs * off-peak). * * This gets a little tricky because we don't know what time it is after * we lose power, and if we're an FCS boot ROM we don't even have the * intrinsics defined. */ WhatTimeIsIt(int* pMinute, int* pHour, int* pMonth, int* pYear, int* pDayOfWeek) { int when, tmpyear; parsesystemtime(7776000); /* 90 days after Jan 1 1970 */ tmpyear = getyear(); when = getdatetimelocal(); /* time in seconds, adjusted for time zone */ parsesystemtime(when); if (tmpyear != 1970 || when < 7776000) { /* either the clock got reset by poweroff, or we're an FCS boot ROM */ printf("TS: time not avail %d/%d", tmpyear, when); *pDayOfWeek = 3; /* Wednesday */ *pHour = 19; /* 7pm */ *pMinute = 0; *pMonth = 1; /* January, 1970 */ *pYear = 1970; return 1; } *pDayOfWeek = getdayofweek(); /* 0-6 */ *pHour = gethour(); /* 0-23 */ *pMinute = getminute(); /* 0-60 */ *pMonth = getmonth(); /* 1-12 */ *pYear = getyear(); /* e.g. 1997 */ return 0; } /* * DialByIndex - dial all numbers in a sequence. * * This is called from the generated PatternDial function (which has the * sequence embedded in it), and calls two other generated functions that * convert an index into data or a function call. */ DialByIndex(char* staticConfig, char* dynamicConfig, char* sequence) { int status, err, nextNumber, sequenceLen, idx; if (version() >= 8) { nextNumber = getconnectretrycount(); } else { nextNumber = 0; } sequenceLen = StrLen(sequence); printf("TS: next=%d, seqLen=%d", nextNumber, sequenceLen); /* * Loop through the (remaining) POPs in the sequence. */ err = 13; /* kTellyBlackHole - only used if nextNumber >= sequenceLen */ for (idx = nextNumber; idx < sequenceLen; idx++) { /*printf("TS: index dialing '%c' (attempt %d)", sequence[idx], idx);*/ status = DialIndexedPOP(staticConfig, dynamicConfig, sequence[idx], idx, &err); /*printf("TS: index dial returned %d, err=%d", status, err);*/ if (status == 0) { err = ChatWithIndexedProvider(sequence[idx]); if (!err || idx == sequenceLen-1) { return err; } else { dialerror(err); #ifndef CLASSIC /* 101223/093628 * Make sure that MsgWatch calls are * limited to the primary POP only, * and to the first trial! */ if (idx == nextNumber) { if (getconnectreason() == 1) { return err; } } #endif } } else if (status == 2 || idx == sequenceLen-1) { return err; } else /*status==1*/ { dialerror(err); #ifndef CLASSIC /* 101223/093628 * Make sure that MsgWatch calls are * limited to the primary POP only */ if (idx == nextNumber) { if (getconnectreason() == 1) { return err; } } #endif } } return err; } /* * main - tellyscript interpreter starts here. * * Initializes the world, then invokes AccessDial or PatternDial. These * are generated automatically right before the script is sent down. * * IMPORTANT: this script can be sent to a 1.2 or later client, and * suddenly find itself executing on a 1.0 client (the boot ROM). It's * important that we don't base flow-of-control decisions on interpreter * version without considering all the effects. */ main() { char *settings = getphonesettings(); char *accessNumber = &settings[64]; char staticConfig[80]; char dynamicConfig[80]; int start = ticks(); int err, result = 0; /* init globals that might be overwritten by InitGeneratedValues */ gUsingOpenISP = 0; gNVRAMMayBeInvalid = 0; /* do this BEFORE anything else, especially GetPhoneSettings */ err = InitGeneratedValues(); if (err) return err; if(version()<9) MaybePrereg(); err = Initialize(staticConfig, dynamicConfig); if (err) return err; /* take control of the status bar (version >= 8) */ setprogressmode(1); /* clear these out every time */ setfullpopnumber(""); setconnectionstats(0, 0, 0, 0); /*printf("DEBUG: at start, num=%d", getconnectretrycount());*/ /* * If the user has set an access number, we use that to the exclusion * of all else, unless they're using the magic '$' feature. * * (AccessDial and PatternDial are generated automatically.) */ if (accessNumber[0] && OStrChr(accessNumber, '$') < 0) { result = AccessDial(staticConfig, dynamicConfig, accessNumber); /* don't blow up if the password is wrong (always want this) */ if (result == 10 && !gUsingOpenISP) /* BadPassword */ result = 15; /* BadPasswordNR */ } else { result = PatternDial(staticConfig, dynamicConfig); } if (!result) { printf("TS: success, in %d sec", (ticks() - start)/60); return 2; /* kTellyLinkConnected */ } else { setconnectretrycount(0); /* temporary fix for 1.3.x */ printf("TS: failure #%d, in %d sec", result, (ticks() - start)/60); setdtr(0); return result; } } #endif /* !FIJI */ /* --- base.tsf END --- */ /* * Locale-specific stuff for USA. */ /* * =========================================================================== * Phone-system-specific stuff * =========================================================================== */ /* * Returns "true" if the number is toll free. This is important because * we don't want to display "Dialing xxx" when we're making toll free calls. */ IsTollFree(char* numberToDial) { if ((numberToDial[1]=='8' && numberToDial[2]=='0' && numberToDial[3]=='0')|| (numberToDial[1]=='8' && numberToDial[2]=='8' && numberToDial[3]=='8')|| (numberToDial[1]=='8' && numberToDial[2]=='7' && numberToDial[3]=='7')|| (numberToDial[1]=='8' && numberToDial[2]=='6' && numberToDial[3]=='6')|| (numberToDial[1]=='8' && numberToDial[2]=='5' && numberToDial[3]=='5')) { /*printf("DEBUG: that looks like a toll-free number!");*/ return 1; } return 0; } /* * Returns "true" if we want to strip off the leading digit. This should * only be called if the "brokenPBX" flag is set. * * This is in "locale.tsf" because we re-used the brokenPBX flag for Japan, * where we want to strip off the leading 0 when doing testing from the US. */ DoStripLeadingDigit(char* numberToDial) { if (*numberToDial == '1') return 1; else return 0; } /* * =========================================================================== * Progress bar messages * =========================================================================== */ SP_PreparingToCall(int perc) { SetProgress("Preparing to call", perc); } SP_DialingNumber(char* prefixStr, char* numberToDial, int perc) { char buffer[128]; /* must hold number(32) + prefix(64) + "Dialing "(8) */ sprintf(buffer, "Dialing %s%s", prefixStr, numberToDial); SetProgress(buffer, perc); } SP_DialingAccessNumber(char* prefixStr, char* numberToDial, int perc) { char buffer[128]; /* must hold number(32)+prefix(64)+"Dialing A/N "(12) */ sprintf(buffer, "Dialing A/N %s%s", prefixStr, numberToDial); SetProgress(buffer, perc); } SP_DialingWebTV(int perc) { SetProgress("Dialing %ServiceName%...", perc); } SP_WaitingToConnect(int perc) { SetProgress("Waiting to connect", perc); } SP_ISPAnswering(int perc) { SetProgress("ISP answering", perc); } SP_WebTVAnswering(int perc) { SetProgress("%ServiceName% answering", perc); } SP_Connecting(int perc) { SetProgress("Connecting", perc); } SP_ConnectedToISP(int perc) { SetProgress("Connecting to your ISP", perc); } SP_ConnectedToWebTV(int perc) { SetProgress("Connecting to %ServiceName%", perc); } SP_StackLow(int perc) { SetProgress("Warning: stack low", perc); }