1/*
2 * Copyright (c) 2000 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24
25/* --------------------------------------------------------------------------
26Includes
27-------------------------------------------------------------------------- */
28
29#include <stdio.h>
30#include <ctype.h>
31#include <time.h>
32#include <fcntl.h>
33#include <signal.h>
34#include <errno.h>
35#include <string.h>
36#include <stdlib.h>
37#include <syslog.h>
38#include <sys/time.h>
39#include <sys/stat.h>
40#include <sys/types.h>
41#include <unistd.h>
42#include <sys/un.h>
43#include <sys/socket.h>
44#include <stdarg.h>
45#include <termios.h>
46#include <sys/ioctl.h>
47#include <sys/param.h>
48#include <CoreFoundation/CoreFoundation.h>
49#include <IOKit/IOCFUnserialize.h>
50#include <SystemConfiguration/SystemConfiguration.h>
51
52#include "CCLArgFunctions.h"
53#include "cclkeys.h"
54
55// import System Configuration schema for keys that are prefs
56#include <SystemConfiguration/SCSchemaDefinitions.h>
57
58#include "CCLEngine.h"
59#include "CCLEngine_defs.h"
60
61/* --------------------------------------------------------------------------
62Defines
63-------------------------------------------------------------------------- */
64
65#define min(a,b)			(((a)<=(b))?(a):(b))
66#define kNormalDialMode     0
67#define kBlindDialMode      1       // CCL term; SC uses is "IgnoreDialTone"
68#define kManualDialMode     2       // user does the dialing
69
70#define DIR_MODEMS_USER     "/Library/Modem Scripts"
71#define DIR_MODEMS_SYS      "/System/Library/Modem Scripts"
72
73
74enum {
75    kUserMsgFLog = 1,
76    kUserMsgFStatus = 2
77};
78
79#define infd 	STDIN_FILENO
80#define outfd 	STDOUT_FILENO
81#define PPP_ARG_FD	3
82
83enum
84{
85    kInvalidTimer = 0,
86    kMatchReadTimer,
87    kPauseTimer,
88    kCharDelayTimer,
89    kBreakTimer
90};
91
92enum enginemode {
93    mode_connect = 0,
94    mode_disconnect,
95    mode_listen
96};
97
98struct	callout {
99    struct timeval	c_time;		/* time at which to call routine */
100    void		*c_arg;		/* argument to routine */
101    void		(*c_func)(void *); /* routine */
102    struct		callout *c_next;
103};
104
105/* --------------------------------------------------------------------------
106Globals
107-------------------------------------------------------------------------- */
108
109u_int8_t gNullString[2];
110
111char	*Commands[] =
112{
113    "!\0",			/*  1 */
114    "@CCLSCRIPT\0",		/*  2 */
115    "@ORIGINATE\0",		/*  3 */
116    "@ANSWER\0",		/*  4 */
117    "@HANGUP\0",		/*  5 */
118    "@LABEL\0",			/*  6 */
119    "ASK\0",			/*  7 */
120    "CHRDELAY\0",		/*  8 */
121    "COMMUNICATINGAT\0",	/*  9 */
122    "DECTRIES\0",		/* 10 */
123    "DTRSET\0",			/* 11 */
124    "DTRCLEAR",			/* 12 */
125    "EXIT",			/* 13 */
126    "FLUSH",			/* 14 */
127    "HSRESET",			/* 15 */
128    "IFANSWER",			/* 16 */
129    "IFORIGINATE",		/* 17 */
130    "IFSTR",			/* 18 */
131    "IFTRIES",			/* 19 */
132    "INCTRIES",			/* 20 */
133    "JUMP",			/* 21 */
134    "JSR",			/* 22 */
135    "LBREAK",			/* 23 */
136    "LOGMSG",			/* 24 */		// undocumented.  use cNote.  KW
137    "MATCHCLR",			/* 25 */
138    "MATCHREAD",		/* 26 */
139    "MATCHSTR",			/* 27 */
140    "NOTE",			/* 28 */
141    "PAUSE",			/* 29 */
142    "RETURN",			/* 30 */
143    "SBREAK",			/* 31 */
144    "SERRESET",			/* 32 */
145    "SETSPEED",			/* 33 */
146    "SETTRIES",			/* 34 */
147    "USERHOOK",			/* 35 */
148    "WRITE",			/* 36 */
149    "MONITORLINE",		/* 37 */
150    "DEBUGLEVEL"		/* 38 */
151};
152
153
154TRScriptVars	SV;			// script variables structure
155u_int8_t	*VarStrings[vsMax+1];
156
157
158//	The following fields are set from values retrieved from the
159//	CCL script 'mlts' resource. The values are received in one
160//	or more Script_ccl_setup messages. See ReadVARMessages().
161//	NOTE: if the script file has no 'mlts' resource, the setup
162//	message is created using a default 'mlts' stored in the
163//	ScriptMod configurator (CCL configurator).
164u_int8_t	DialString1MaxLen;
165u_int8_t	DialString2MaxLen;
166u_int8_t	DialString3MaxLen;
167
168u_int8_t	VerboseBuffer[256];//	Buffer for forming text for verbose log messages.
169u_int32_t	LastExitError;
170
171// Spackle a security hole.  Was the last ask masked?  if so,
172// the next write we do should log masked.
173// Don't forget to set and clear this flag in later asks and
174// after the write
175u_int32_t	LastAskedMasked;
176
177FILE* filefd   = (FILE *) 0;
178char *filename    = NULL;
179char *phone_num   = "";
180char *username  = "";
181char *password  = "";
182char *alertname  = "";
183char *cancelname  = "";
184char *iconurl  = "";
185CFStringRef	alertNameRef = 0;
186CFStringRef	cancelNameRef = 0;
187CFURLRef	iconURL = 0;
188
189char*		localBunPath  = "";
190CFURLRef	localBundleURL = NULL;
191
192CFBundleRef cclBundle = NULL;
193CFURLRef	cclBundleURL= NULL;
194CFURLRef	appBundleURL= NULL;
195
196
197
198int pulse = 0;
199int dialmode = 0; // 0 = normal, 1 = blind(ignoredialtone), 2 = manual
200int speaker = 1;
201int errorcorrection = 1;
202int datacompression = 1;
203u_char *serviceID   = NULL;
204
205/*
206int cellphoneCID= 0;
207int cellphoneQofS= 0;
208int limitBandwidth= 0;
209*/
210
211int verbose 	  = 0;
212u_int32_t debuglevel 	  = 0;
213int sysloglevel = LOG_NOTICE;
214int syslogfacility = 0; //LOG_RAS; Kevin- variable not defined, punt...
215int usestderr 	= 1;
216int signalerror = 0;
217
218struct callout *callout = NULL;	/* Callout list */
219struct timeval timenow;		/* Current time */
220enum enginemode enginemode = mode_connect;
221
222fd_set	allset;
223int 	maxfd;
224
225/* --------------------------------------------------------------------------
226Forward Declarations
227-------------------------------------------------------------------------- */
228void SetVarString(u_int32_t vs, u_int8_t * data);
229u_int8_t *GetVarString(u_int32_t vs);
230int PrepScript();
231u_int8_t NextLine();
232int NextCommand();
233int NextInt(u_int32_t *theIntPtr);		// XXX returns signed numbers!
234void PrepStr(u_int8_t *destStr, u_int32_t *isVarString, u_int32_t *varIndex, int varSubstitution);
235void SkipBlanks();
236void RunScript();
237int MatchStr();
238void Note();
239u_int8_t Write();
240void CommunicatingAt();
241u_int8_t Ask();
242void WriteContinue();
243void UserHook();
244void ScheduleTimer(long type, u_int32_t milliseconds);
245int IfStr();
246
247void MatchClr();
248void Break();
249void SetSpeed(void);
250void SerReset(void);
251void MonitorLine();
252void DebugLevel();
253void HSReset();
254void Flush();
255void DTRCommand(short DTRCode);
256
257void terminate(int exitError);
258void InitScript();
259void Play();
260void calltimeout(void);
261struct timeval *timeleft(struct timeval *);
262void timeout(void (*func)(void *), void *arg, u_long time);
263void untimeout(void (*func)(void *), void *arg);
264void ReceiveMatchData(u_int8_t nextChar);
265int publish_entry(u_char *serviceid, CFStringRef entry, CFTypeRef value);
266int unpublish_entry(u_char *serviceid, CFStringRef entry);
267bool SetVarStringFromDict(CFDictionaryRef dict, CFStringRef key, int varStringIndex);
268void sLog(char *fmt, ...);
269
270
271
272/*************** Micro getopt() *********************************************/
273#define	OPTION(c,v)	(_O&2&&**v?*(*v)++:!c||_O&4?0:(!(_O&1)&& \
274                                (--c,++v),_O=4,c&&**v=='-'&&v[0][1]?*++*v=='-'\
275                                &&!v[0][1]?(--c,++v,0):(_O=2,*(*v)++):0))
276#define	OPTARG(c,v)	(_O&2?**v||(++v,--c)?(_O=1,--c,*v++): \
277                                (_O=4,(char*)0):(char*)0)
278#define	OPTONLYARG(c,v)	(_O&2&&**v?(_O=1,--c,*v++):(char*)0)
279#define	ARG(c,v)	(c?(--c,*v++):(char*)0)
280
281/*************** Micro getopt() *********************************************/
282
283
284/* --------------------------------------------------------------------------
285-------------------------------------------------------------------------- */
286void badsignal(int signo)
287{
288    signalerror = cclErr_NoMemErr;
289}
290
291/* --------------------------------------------------------------------------
292-------------------------------------------------------------------------- */
293void hangup(int signo)
294{
295	// 7341084: ignore signals during disconnection
296	if (enginemode != mode_disconnect)
297		signalerror = cclErr_ScriptCancelled;
298}
299
300
301/* --------------------------------------------------------------------------
302-------------------------------------------------------------------------- */
303void StartRead()
304{
305    FD_SET(infd, &allset);
306    maxfd = 1;
307}
308
309/* --------------------------------------------------------------------------
310-------------------------------------------------------------------------- */
311void StopRead()
312{
313    FD_ZERO(&allset);
314    maxfd = 0;
315}
316
317#pragma mark Bundle Configuration/Startup
318
319/* --------------------------------------------------------------------------
320SetVarStringFromDict
321- handles pulling the data out and setting the var value for the passed in key
322- Returns success (true) even if the value is missing from the input dict
323- fails only if the data is not of string type or if it can't be extracted
324-------------------------------------------------------------------------- */
325bool SetVarStringFromDict(CFDictionaryRef dict, CFStringRef key, int varStringIndex)
326{
327    bool retVal= false;
328	u_char 	text[256];
329	CFStringRef varString;
330
331	varString= (CFStringRef)CFDictionaryGetValue(dict, key);
332	if(varString == NULL) {
333		retVal = true;		// missing values are okay (will return gNullString)
334	} else if(CFStringGetTypeID() == CFGetTypeID(varString)) {
335		// X varStrings are defined to be in the system encoding?
336		if(CFStringGetPascalString(varString,text,256,CFStringGetSystemEncoding()))
337		{
338			SetVarString(varStringIndex, text);
339			retVal= true;
340		}
341	} else {
342		sLog("SetVarStringFromDict: value with non-string type");
343		CFShow(key);
344		CFShow(varString);
345	}
346	return retVal;
347}
348
349/* --------------------------------------------------------------------------
350// check to make sure that the CCL version is the one we know
351-------------------------------------------------------------------------- */
352bool VerifyCCLBundle(CFBundleRef cclBun)
353{
354	CFDictionaryRef infoDict= CFBundleGetInfoDictionary(cclBun);
355	int verNum = -1;
356
357	if(!infoDict || !GetIntFromDict(infoDict, &verNum, kCCLVersionKey))
358		return false;
359
360	return (verNum == kCCLBundleVersion);
361}
362
363/* --------------------------------------------------------------------------
364XX: should merge in keys in case we want to store CCLParameters in SC
365Missing values are ignored by SetVarStringFromDict
366-------------------------------------------------------------------------- */
367bool ConfigureCCLParameters(CFDictionaryRef personalityDict)
368{
369	bool rval = false;
370	CFDictionaryRef cclParms= GetCFDictionaryFromDict(personalityDict, kCCLParametersKey);
371	if(cclParms)
372	{
373		rval = SetVarStringFromDict(cclParms, kCCLVarString27Key, vsString27);
374		rval &= SetVarStringFromDict(cclParms, kCCLVarString28Key, vsString28);
375		rval &= SetVarStringFromDict(cclParms, kCCLVarString29Key, vsString29);
376		rval &= SetVarStringFromDict(cclParms, kCCLVarString30Key, vsString30);
377		rval&=SetVarStringFromDict(cclParms,kCCLConnectSpeedKey,vsConnectSpeed);
378		rval &= SetVarStringFromDict(cclParms, kCCLInitStringKey, vsInit);
379
380		// Preferred* keys are handled by libccl's cclparser + SC.
381		// The user has to confirm that they want those values.
382	}
383	return rval;
384}
385
386/* --------------------------------------------------------------------------
387-------------------------------------------------------------------------- */
388CFURLRef
389CopyScriptWithPersonality(CFBundleRef bundleRef, CFStringRef personalityKey)
390{
391	CFURLRef rval = NULL;
392	CFDictionaryRef infoDict;
393	CFDictionaryRef persDict;
394	CFDictionaryRef persEntry = NULL;
395
396	if (!(infoDict = CFBundleGetInfoDictionary(bundleRef)))
397		goto finish;
398	if (!(persDict = GetCFDictionaryFromDict(infoDict, kCCLPersonalitiesKey)))
399		goto finish;
400
401	// check the provided personality key
402	if (personalityKey)
403		persEntry = GetCFDictionaryFromDict(persDict, personalityKey);
404	// or try the default personality (X could mask missing personality)
405	if (!persEntry)
406		persEntry = GetCFDictionaryFromDict(persDict,kCCLDefaultPersonalityKey);
407
408	// if we found something, get the script name
409	if (persEntry) {
410		CFStringRef scriptName = NULL;
411
412		if (!GetCFStringFromDict(persEntry,&scriptName,kCCLScriptNameKey))
413			goto finish;
414		if (!scriptName)
415			goto finish;
416
417		rval = CFBundleCopyResourceURL(bundleRef, scriptName, NULL, NULL);
418		if (rval) {
419			// We do this here to avoid having to determinepersonality twice...
420			if (!ConfigureCCLParameters(persEntry)) {
421				CFRelease(rval);
422				rval = NULL;
423			}
424			// modemDict CCLParameters (if any) merged in later
425		}
426	}
427
428finish:
429	return rval;
430}
431
432/* --------------------------------------------------------------------------
433-------------------------------------------------------------------------- */
434bool ConfigureWithBundle(CFBundleRef bundleRef, CFStringRef personality)
435{
436	bool retVal= false;
437	UInt8 pathBuffer[MAXPATHLEN];
438
439	if(bundleRef!= NULL)
440	{
441		if(VerifyCCLBundle(bundleRef))
442		{
443			CFURLRef scriptURL= CopyScriptWithPersonality(bundleRef, personality);
444			if(scriptURL!= NULL)
445			{
446				if(CFURLGetFileSystemRepresentation(scriptURL, true, pathBuffer, MAXPATHLEN))
447				{
448					if (filename)	free(filename);
449					filename = strdup((char*)pathBuffer);
450					retVal= true;
451					CFRetain(bundleRef);
452					cclBundle= bundleRef;
453				}
454				CFRelease(scriptURL);
455			}
456		}
457	}
458	return retVal;
459}
460
461/* --------------------------------------------------------------------------
462ResolveCCLPath() resolves the full path with the following preferences:
463- /full/path/to/foo.ccl
464- /full/path/to/foo
465- /System/Library/Modem Scripts/foo.ccl
466- /System/Library/Modem Scripts/foo
467- /Library/Modem Scripts/foo.ccl
468- /Library/Modem Scripts/foo
469Returns true if it found something
470Side Effects: fullpath used as scratch space
471
472Fairly common case should (eventually) be fully-resolved .ccl path.
473Note: even if foo exists; prefers foo.ccl
474-------------------------------------------------------------------------- */
475bool ResolveCCLPath(char *path, char fullpath[PATH_MAX], struct stat *sb)
476{
477	char *extptr = path + (strlen(path) - (kCCL_BundleExtLen + sizeof('.')));
478	bool cclExt;
479
480	// try to avoid an unnecessary stat()
481	cclExt = (*extptr == '.') && (strcmp(extptr+1, kCCL_BundleExtension) == 0);
482
483	if (path[0] == '/') {
484		if (!cclExt) {
485			snprintf(fullpath, PATH_MAX, "%s.%s", path, kCCL_BundleExtension);
486			if (stat(fullpath, sb) == 0) 	return true;
487		}
488		strlcpy(fullpath, path, PATH_MAX);
489		if (stat(fullpath, sb) == 0) 		return true;
490	} else {
491		// path is not absolute so try the common directories
492		if (!cclExt) {
493			snprintf(fullpath, PATH_MAX, "%s/%s.%s", DIR_MODEMS_SYS, path,
494					kCCL_BundleExtension);
495			if (stat(fullpath, sb) == 0)	return true;
496		}
497		snprintf(fullpath, PATH_MAX, "%s/%s", DIR_MODEMS_SYS, path);
498		if (stat(fullpath, sb) == 0)		return true;
499
500		if (!cclExt) {
501			snprintf(fullpath, PATH_MAX, "%s/%s.%s", DIR_MODEMS_USER, path,
502					kCCL_BundleExtension);
503			if (stat(fullpath, sb) == 0)	return true;
504		}
505		snprintf(fullpath, PATH_MAX, "%s/%s", DIR_MODEMS_USER, path);
506		if (stat(fullpath, sb) == 0)		return true;
507	}
508
509	return false;
510}
511
512#define LOG_MAX 1024
513/* --------------------------------------------------------------------------
514sLog(): capture syslog/stderr idiom that was scattered throughout the code
515-------------------------------------------------------------------------- */
516void sLog(char *fmt, ...)
517{
518	va_list ap;
519	char taggedfmt[LOG_MAX], s[64];
520	time_t t;
521
522
523	if (sysloglevel) {
524		va_start(ap, fmt);
525		vsyslog(sysloglevel, fmt, ap);
526		va_end(ap);
527	}
528	if (usestderr) {
529		time(&t);
530		strftime(s, sizeof(s), "%c : ", localtime(&t));
531		strlcpy(taggedfmt, s, LOG_MAX);
532		strlcat(taggedfmt, fmt, LOG_MAX);
533		strlcat(taggedfmt, "\n", LOG_MAX);
534		va_start(ap, fmt);
535		vfprintf(stderr, taggedfmt, ap);
536		va_end(ap);
537	}
538}
539
540/* --------------------------------------------------------------------------
541validate path and see if it's a bundle; otherwise it's an old-school flat CCL
542-------------------------------------------------------------------------- */
543bool ConfigureCCL(char* path, CFStringRef personality)
544{
545	bool retVal = false;
546	CFURLRef url;
547	CFBundleRef  bundleRef;
548	char fullpath[PATH_MAX];
549	struct stat sb;
550
551	// it's possible we have only a name
552	// we need to turn it into a path and perhaps add .ccl to it
553	if (!ResolveCCLPath(path, fullpath, &sb)) {
554		sLog("couldn't find CCL '%s'", path);
555		goto finish;
556	}
557
558	url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
559		(UInt8*)fullpath, strlen(fullpath), S_ISDIR(sb.st_mode));
560	if (!url)	goto finish;
561
562	bundleRef= CFBundleCreate(kCFAllocatorDefault, url);
563	if(bundleRef) {
564		// retains cclBundle, copies filename, etc
565		retVal = ConfigureWithBundle(bundleRef, personality);
566		CFRelease(bundleRef);
567		if (retVal)
568			cclBundleURL = url;		// keep for loc
569		else {
570			CFRelease(url);
571			sLog("%s does not appear to be a CCL bundle", fullpath);
572		}
573	} else {
574		// maybe it was "old school"
575		if (S_ISREG(sb.st_mode)) {
576			CFRelease(url);
577			if (filename)	free(filename);
578			filename = strdup(fullpath);
579			retVal = true;
580		} else {
581			sLog("%s is neither a bundle nor a file", filename);
582		}
583	}
584
585finish:
586	return retVal;
587}
588
589/* --------------------------------------------------------------------------
590-------------------------------------------------------------------------- */
591bool ParseEngineDict(CFDictionaryRef argDict)
592{
593	bool success= false;
594
595	CFDictionaryRef engineDict= GetCFDictionaryFromDict(argDict, kCCLEngineDictKey);
596	if(engineDict!= NULL)
597	{
598		CFStringRef modeString= (CFStringRef) CFDictionaryGetValue(engineDict, kCCLEngineModeKey);
599		if(modeString!= NULL)
600		{
601			// we assumed mode_connect when we initialized enginemode
602			if((CFStringGetTypeID()== CFGetTypeID(modeString)) && (CFStringCompare(modeString, kCCLEngineModeDisconnect, 0)== kCFCompareEqualTo))
603			{
604				enginemode = mode_disconnect;
605			}
606			// Get*FromDict return success if the value was missing
607			// i.e. the only errors are from conversion problems
608			success = CopyCStringFromDict(engineDict, &alertname, kCCLEngineAlertNameKey);
609			success &= CopyCStringFromDict(engineDict, &localBunPath, kCCLEngineBundlePathKey);
610			success &= CopyCStringFromDict(engineDict, &cancelname, kCCLEngineCancelNameKey);
611			success &= CopyCStringFromDict(engineDict, &iconurl, kCCLEngineIconPathKey);
612			success &= GetIntFromDict(engineDict, &usestderr, kCCLEngineLogToStdErrKey);
613			success &= CopyCStringFromDict(engineDict, (char**) &serviceID, kCCLEngineServiceIDKey);
614			success &= GetIntFromDict(engineDict, &syslogfacility, kCCLEngineSyslogFacilityKey);
615			success &= GetIntFromDict(engineDict, &sysloglevel, kCCLEngineSyslogLevelKey);
616			success &= GetIntFromDict(engineDict, &verbose, kCCLEngineVerboseLoggingKey);
617		}
618	}
619
620	return success;
621}
622
623/* --------------------------------------------------------------------------
624-------------------------------------------------------------------------- */
625bool ParseModemDict(CFDictionaryRef argDict)
626{
627	bool success= false;
628	CFDictionaryRef modemDict= GetCFDictionaryFromDict(argDict, kSCEntNetModem);
629
630	// CFShow(modemDict);
631	if(modemDict!= NULL)
632	{
633		char* tempFilePath = NULL;
634		CFStringRef personality = NULL;
635
636		if (!CopyCStringFromDict(modemDict, &tempFilePath, kSCPropNetModemConnectionScript))
637			goto finish;
638		if (!tempFilePath)
639			goto finish;
640		if (!GetCFStringFromDict(modemDict, &personality, kSCPropNetModemConnectionPersonality))
641			goto finish;
642
643		// ConfigureCCL extracts default CCLParameters, handles NULL personality
644		success = ConfigureCCL(tempFilePath, personality);
645		free(tempFilePath);
646		if (!success)
647			goto finish;
648
649		// dialmode is a string which we convert
650		dialmode= kNormalDialMode;		// assume normal dial
651		CFStringRef dialModeStr = (CFStringRef) CFDictionaryGetValue(modemDict, kSCPropNetModemDialMode);
652		if(dialModeStr && CFStringGetTypeID()==CFGetTypeID(dialModeStr)){
653			if (kCFCompareEqualTo == CFStringCompare(dialModeStr,
654					kSCValNetModemDialModeIgnoreDialTone,0))
655				dialmode = kBlindDialMode;
656			else if (kCFCompareEqualTo == CFStringCompare(dialModeStr,
657					kSCValNetModemDialModeManual,0))
658				dialmode = kManualDialMode;
659		}
660
661		// only needed for mode_connect
662		if (enginemode == mode_connect) {
663			// expected, but we have reasonable defaults
664			success &= GetIntFromDict(modemDict, &datacompression, kSCPropNetModemDataCompression);
665			success &= GetIntFromDict(modemDict, &errorcorrection, kSCPropNetModemErrorCorrection);
666			success &= CopyCStringFromDict(modemDict,&phone_num,kModemPhoneNumberKey);
667			success &= GetIntFromDict(modemDict, &pulse, kSCPropNetModemPulseDial);
668			success &= GetIntFromDict(modemDict, &speaker, kSCPropNetModemSpeaker);
669			success &= CopyCStringFromDict(modemDict, &username, kSCPropNetPPPAuthName);
670			success &= CopyCStringFromDict(modemDict, &password, kSCPropNetPPPAuthPassword);
671
672
673			// optional, and SetVarStringFromDict sets to gNull if missing
674			success &= SetVarStringFromDict(modemDict, kSCPropNetModemAccessPointName, vsAPN);
675			success &= SetVarStringFromDict(modemDict, kSCPropNetModemDeviceContextID, vsCID);
676		}
677
678		// speed might be needed for disconnect?
679		success &= SetVarStringFromDict(modemDict, kSCPropNetModemConnectSpeed, vsConnectSpeed);
680		// stored kSCPropNetModemConnectSpeed could override CCL's static config
681		// X could call ConfigureCCLParameters with modem dict (SC) version here
682	}
683
684finish:
685	return success;
686}
687
688
689/* --------------------------------------------------------------------------
690-------------------------------------------------------------------------- */
691bool ParseArgDictionary(CFDictionaryRef argDict)
692{
693	CFBundleRef mainBundle= NULL;
694
695	if(ParseEngineDict(argDict)) {
696		if(ParseModemDict(argDict)) {
697			mainBundle= CFBundleGetMainBundle();
698			if(mainBundle)
699				appBundleURL= CFBundleCopyBundleURL(mainBundle);
700			return true;
701		}
702	}
703
704	return false;
705}
706
707
708/* --------------------------------------------------------------------------
709-------------------------------------------------------------------------- */
710#define kParseBufferSize 4096
711bool ParsePipeArgs()
712{
713    ssize_t  bRead= 0;
714    ssize_t  tbRead= 0;
715
716    bool retVal= false;
717
718    void* tempBuf;
719    char* dataBuf= malloc(kParseBufferSize);
720    if (!dataBuf)   goto finish;
721
722    bRead= read(PPP_ARG_FD, dataBuf,  kParseBufferSize);
723    tbRead= bRead;
724    while (bRead == kParseBufferSize) {
725        tempBuf= realloc(dataBuf, (tbRead+ kParseBufferSize));
726        if (tempBuf) {
727            dataBuf= tempBuf;
728            bRead= read(PPP_ARG_FD, &dataBuf[tbRead], kParseBufferSize);
729            tbRead+= bRead;
730        } else {
731            goto finish;
732        }
733
734    }
735    CFDictionaryRef argDict= IOCFUnserialize(dataBuf, kCFAllocatorDefault, kCFPropertyListImmutable, NULL);
736    if(argDict!= NULL)
737    {
738        retVal= ParseArgDictionary(argDict);
739    }
740
741finish:
742    if (dataBuf)    free(dataBuf);
743    close(PPP_ARG_FD);
744    return retVal;
745}
746
747/* --------------------------------------------------------------------------
748-------------------------------------------------------------------------- */
749int main(int argc, char **argv)
750{
751    char            c;
752    int             ret, nready, status, i;
753    struct stat     statbuf;
754    fd_set          rset;
755    struct timeval  timo;
756
757/* move DEBUG stuff (below) here if ppp/CCLEngine interactions need debugging */
758
759    signal(SIGHUP, hangup);			/* Hangup */
760    signal(SIGINT, hangup);			/* Interrupt */
761    signal(SIGTERM, hangup);		/* Terminate */
762    signal(SIGCHLD, badsignal);
763    signal(SIGUSR1, badsignal);
764    signal(SIGUSR2, badsignal);
765    signal(SIGABRT, badsignal);
766    signal(SIGALRM, badsignal);
767    signal(SIGFPE, badsignal);
768    signal(SIGILL, badsignal);
769    signal(SIGPIPE, badsignal);
770    signal(SIGQUIT, badsignal);
771    signal(SIGSEGV, badsignal);
772
773	// zero all of the strings (removed from InitScript)
774    for (i = 0; i <= vsMax; i++)
775        VarStrings[i] = 0;
776
777    openlog("ccl", LOG_PID | LOG_NDELAY, syslogfacility);
778
779    if(!ParsePipeArgs())
780        terminate(cclErr_BadParameter);
781
782/* DEBUG for attach on connect only (don't sweat the PID;
783   (gdb) attach CCLEngine * /
784if (enginemode == mode_connect) {
785fprintf(stderr, "CCLEngine pid %d\n", getpid());
786fflush(stderr);
787sleep(3);
788}
789*/
790
791    InitScript();       // sets whatever variables PPA() didn't
792
793    alertNameRef = CFStringCreateWithCString(NULL, alertname, kCFStringEncodingUTF8);
794    cancelNameRef = CFStringCreateWithCString(NULL, cancelname, kCFStringEncodingUTF8);
795    if (*iconurl)
796        iconURL = CFURLCreateFromFileSystemRepresentation(nil, (UInt8*) iconurl, strlen(iconurl), false /*notDir*/);
797    if (*localBunPath)
798        localBundleURL = CFURLCreateFromFileSystemRepresentation(NULL, (UInt8*) localBunPath, strlen(localBunPath), true /*isDir*/);
799
800    // figure out file size
801	ret = stat(filename, &statbuf);
802	if (ret < 0)
803		terminate(cclErr_BadParameter);
804
805    SV.script = malloc((int)statbuf.st_size);
806    if (!SV.script)
807        terminate(cclErr_NoMemErr);
808
809    SV.scriptSize = (int)statbuf.st_size;
810
811    filefd = fopen(filename, "r");
812    if (filefd) {
813        ret = fread(SV.script, statbuf.st_size, 1, filefd);
814        if (ret < 0)
815            terminate(cclErr_BadParameter);
816    }
817
818    /* start by unpublishing any remaining information */
819    if (serviceID) {
820        unpublish_entry((u_char*) serviceID, kSCPropNetModemConnectSpeed);
821    }
822    StopRead();
823    Play();
824
825    for ( ; ; ) {
826
827        rset = allset;
828        nready = select(maxfd, &rset, NULL, NULL, timeleft(&timo));
829        if(signalerror)
830			terminate(signalerror);
831        if (FD_ISSET(infd, &rset)) {
832            status = read(infd, &c, 1);
833            switch (status) {
834                case 1:
835                    ReceiveMatchData(c);
836                    break;
837                default:
838                    if (errno != EINTR)
839                        terminate(cclErr);
840            }
841        }
842
843        calltimeout();
844
845    }
846
847    // NOT REACHED
848    exit(0);       // insure the process exit status is 0
849    return 0;      // ...and make main fit the ANSI spec.
850}
851
852/* --------------------------------------------------------------------------
853 init everything
854-------------------------------------------------------------------------- */
855void InitScript()
856{
857    u_char 	text[256], text1[256];
858
859    gNullString[0] = 1;
860    gNullString[1] = ' ';
861
862    //
863    // Initialize script variables and varStrings
864    //
865    bzero(&SV, sizeof(struct TRScriptVars));
866
867    LastExitError		= 0;
868
869    DialString1MaxLen		= 40;
870    DialString2MaxLen		= 40;
871    DialString3MaxLen		= 40;
872
873    SV.topOfStack		= cclNestingLimit;	// this stack is for JSR and RETURN commands.
874    SV.theAbortErr		= 0;
875    SV.lineCount		= 0;
876    SV.scriptPrepped		= 0;		// PrepScript has not yet been called
877    SV.scriptPrepFailed		= 0;		// PrepScript has not yet rejected the script
878    SV.scriptAllocSize		= 0;
879    SV.scriptSize		= 0;
880    SV.script			= NULL;
881    SV.askLabel			= 0;
882    SV.indexTable		= NULL;
883    SV.commands			= NULL;
884
885    LastAskedMasked		= 0;
886
887
888    text[0] = strlen(username);
889    bcopy(username, &text[1], text[0]);
890    SetVarString(vsUserName, text);
891
892    text[0] = strlen(password);
893    bcopy(password, &text[1], text[0]);
894    SetVarString(vsPassWord, text);
895
896    sprintf((char*) text, "%d", speaker);
897    text1[0] =  strlen((char*) text);
898    bcopy(text, &text1[1], text[0]);
899    SetVarString(vsModemSpeaker, text1);
900
901    sprintf((char*) text, "%d", errorcorrection);
902    text1[0] =  strlen((char*) text);
903    bcopy(text, &text1[1], text[0]);
904    SetVarString(vsErrorCorrection, text1);
905
906    sprintf((char*) text, "%d", datacompression);
907    text1[0] =  strlen((char*) text);
908    bcopy(text, &text1[1], text[0]);
909    SetVarString(vsDataCompression, text1);
910
911    sprintf((char*) text, "%d", dialmode);
912    text1[0] =  strlen((char*) text);
913    bcopy(text, &text1[1], text[0]);
914    SetVarString(vsDialMode, text1);
915
916    text1[0] =  1;
917    text1[1] =  pulse ? 'P' : 'T';
918    SetVarString(vsTonePulse, text1);
919}
920
921/* --------------------------------------------------------------------------
922 dispose everything
923-------------------------------------------------------------------------- */
924void DisposeScript()
925{
926    int 	i;
927
928    if (SV.script) {
929        free(SV.script);
930        SV.script = 0;
931    }
932
933    if (SV.commands) {
934        free(SV.commands);
935        SV.commands = 0;
936    }
937
938    if (SV.indexTable) {
939        free(SV.indexTable);
940        SV.indexTable = 0;
941    }
942
943    for (i = 0; i <= vsMax; i++) {
944        if (VarStrings[i]) {
945            free(VarStrings[i]);
946            VarStrings[i] = 0;
947        }
948    }
949}
950
951/* --------------------------------------------------------------------------
952varStrings are held in the global VarStrings[].  Each element is NULL
953until SetVarString is called, in which case memory is allocated and the
954data copied in.  If Set has not previously succeeded, Get will return a
955pointer to gNullString, a zero length pstring.  Memory errors don't abort
956the script but they may break it if the empty string wasn't acceptable
957for that particular varString's value.
958-------------------------------------------------------------------------- */
959void SetVarString(u_int32_t vs, u_int8_t * data)
960{
961    if (vs > vsMax)
962        return;
963
964    if (VarStrings[vs]) {
965        free(VarStrings[vs]);
966        VarStrings[vs] = 0;
967    }
968
969    if (data) {
970        VarStrings[vs] = malloc(*data + 1);
971        if (VarStrings[vs])
972            bcopy(data, VarStrings[vs], *data + 1 );
973		else
974			sLog("SetVarString: %s", strerror(errno));	// should be ENOMEM
975    }
976}
977
978/* --------------------------------------------------------------------------
979-------------------------------------------------------------------------- */
980u_int8_t *GetVarString(u_int32_t vs)
981{
982    if (vs > vsMax)
983	{
984        return gNullString;
985	}
986    if (VarStrings[vs])
987	{
988        return VarStrings[vs];
989	}
990    return gNullString;
991}
992
993
994/* --------------------------------------------------------------------------
995called to start execution of the script.  this is true regardless as
996to the connect/answer mode set by SetAnswering().
997-------------------------------------------------------------------------- */
998void Play(void)
999{
1000    u_int32_t	remaining;
1001    u_int8_t	*src, len = 0, buf[256];
1002    u_char	text[256];
1003
1004    LastExitError = 0;
1005
1006    if (!SV.scriptPrepped ) { // if the Script has not yet been preflighted...
1007        if (SV.theAbortErr = PrepScript()) { // if the preflight returns an error...
1008            SV.scriptPrepFailed = 1;		// so Disconnect won't try to re-preflight the script.
1009	    sLog("Script parsing failed after %d lines", SV.scriptLine);
1010            terminate(SV.theAbortErr);	// a script syntax error was detected
1011            return;
1012        }
1013        else
1014            SV.scriptPrepped = 1;
1015    }
1016
1017    SV.chrDelayValue	= 0;
1018
1019    switch (enginemode) {
1020        case mode_connect:
1021            SV.ctlFlags		&= ~cclHangupMode;
1022            SV.ctlFlags |= cclOriginateMode;		// turn on OriginateMode
1023            SV.scriptLine = SV.originateLine;		// set the script entry point
1024
1025            // if the dial string has not yet arrived in a Script_ccl_setup message,
1026            // fetch the dial string from ScriptMod, which received it from either a
1027            // T_conn_req or a Script_execute message.
1028
1029			// XX should an empty phone number string -> gNullString?
1030            text[0] =  strlen(phone_num);
1031            bcopy(phone_num, &text[1], text[0]);
1032            if (GetVarString(vsDialString) == gNullString)
1033                SetVarString(vsDialString, text);
1034
1035            SetVarString(vsDialString1, NULL);
1036            SetVarString(vsDialString2, NULL);
1037            SetVarString(vsDialString3, NULL);
1038
1039            if (src = GetVarString(vsDialString)) {
1040
1041                remaining = *src++;
1042
1043                if (len = min(DialString1MaxLen, remaining)) {
1044                    *buf = len;
1045                    bcopy(src, buf+1, len);
1046                    SetVarString(vsDialString1, buf);
1047                    src += len;
1048                    remaining -= len;
1049                }
1050
1051                if (len = min(DialString2MaxLen, remaining)) {
1052                    *buf = len;
1053                    bcopy(src, buf+1, len);
1054                    SetVarString(vsDialString2, buf);
1055                    src += len;
1056                    remaining -= len;
1057                }
1058
1059                if (len = min(DialString3MaxLen, remaining)) {
1060                    *buf = len;
1061                    bcopy(src, buf+1, len);
1062                    SetVarString(vsDialString3, buf);
1063                    src += len;
1064                    remaining -= len;
1065                }
1066
1067                // and ignore any remaining characters
1068            }
1069
1070           break;
1071        case mode_disconnect:
1072            SV.ctlFlags	&= ~cclAnswerMode;		// though we'll return to Answering, on disconnect
1073            SV.ctlFlags	|= cclHangupMode;
1074            SV.scriptLine = SV.hangUpLine;		// set the script entry point
1075            break;
1076        case mode_listen:
1077            SV.ctlFlags |= cclAnswerMode;		// turn on AnswerMode
1078            SV.ctlFlags		&= ~cclHangupMode;
1079            SV.scriptLine = SV.answerLine;		// set the script entry point
1080            break;
1081    }
1082
1083    RunScript();	// plays the script from @entrypoint until Exit
1084}
1085
1086/* --------------------------------------------------------------------------
1087This routine prepares a script to be played.
1088CCL variablesa and labels are set up for playing the script, and commands are checked.
1089-------------------------------------------------------------------------- */
1090int PrepScript()
1091{
1092    u_int8_t	*bp, *d, *s, c;
1093    u_int16_t	*indexp, Lindex, i, cmd;
1094    u_int32_t	labelIndex;
1095    int			result = 0;
1096
1097    // The following "do {} while (false);" construct
1098    // prepares for the task of looping through the lines
1099    // of script. If there is something wrong with script
1100    // or if it can't allocate memory, it breaks immediately
1101    // with an error value in "result".
1102
1103    do {
1104        if (SV.scriptSize > MAX_SCRIPT_SIZE) {
1105            result = cclErr_ScriptTooBig;
1106            break;
1107        }
1108
1109        Lindex = 1;
1110        bp = SV.script;
1111
1112        // count the lines in the script:
1113        for( i = SV.scriptSize; i; --i ) {
1114            if ((*bp == chrCR) || (*bp == chrNL))
1115                Lindex++;
1116            bp++;
1117        }
1118
1119        if (Lindex > MAX_SCRIPT_LINES) {
1120            result = cclErr_TooManyLines;
1121            break;
1122        }
1123
1124		// Lindex is 1-based; lineCount is zero-based
1125		// However, if the last character wasn't a line terminator; bump
1126        SV.lineCount = Lindex - 1;
1127        c = SV.script[SV.scriptSize-1];
1128        if (c != chrCR && c != chrNL)
1129            SV.lineCount++;
1130
1131        bp = malloc(500);
1132        if (!bp) {
1133            result = ENOMEM;
1134            break;
1135        }
1136
1137        SV.commands = bp;
1138        *((u_int16_t *)bp) = cLastCmd;
1139        bp += 2;
1140        for( i = 0; i < cLastCmd; ) {
1141            s = (u_int8_t*) Commands[i++];
1142            d = bp + 1;
1143            while( c = *s++ )
1144                *d++ = c;
1145            *bp = (d - bp - 1);		// set count for pascal string
1146            bp = d;
1147        }
1148
1149        SV.indexTable = malloc(SV.lineCount * sizeof(u_int16_t *));
1150        if (!SV.indexTable) {
1151            result = ENOMEM;
1152            break;
1153        }
1154
1155        indexp = SV.indexTable;
1156        for( i = 0; ++i <= SV.lineCount; )
1157            *indexp++ = 0;
1158
1159        /* adjust the size of line index array	*/
1160        indexp = SV.indexTable;
1161
1162        /* fill line index array, for each line,		*/
1163        /* SV.indexTable->lineIndex == byte offset	*/
1164        /* from start of script to start of line.		*/
1165        bp = SV.script;
1166        Lindex = 0;
1167        *indexp++ = 0;
1168        for (i = 0; i < SV.scriptSize; i++) {
1169            if ((*bp == chrCR) || (*bp == chrNL))
1170                *indexp++ = i + 1;
1171            bp++;
1172        }
1173
1174        SV.scriptLine = 0;
1175    }
1176    while (0);
1177
1178    // The initialization loop above may have set
1179    // result != noErr. If so, the following while
1180    // loop is not entered.
1181
1182    while (result == 0 && NextLine()) {	/*process the next line of the script*/
1183        cmd = NextCommand();
1184		switch (cmd)  {
1185            case cNoCmd:
1186                result = cclErr_BadCommand;
1187                break;
1188
1189            case cOriginateLabel:
1190                if( SV.originateLine )
1191                    result = cclErr_DuplicateLabel;
1192                else
1193                    SV.originateLine = SV.scriptLine;
1194                break;
1195
1196            case cAnswerLabel:
1197                if( SV.answerLine )
1198                    result = cclErr_DuplicateLabel;
1199                else
1200                    SV.answerLine = SV.scriptLine;
1201                break;
1202
1203            case cHangUpLabel:
1204                if( SV.hangUpLine )
1205                    result = cclErr_DuplicateLabel;
1206                else
1207                    SV.hangUpLine = SV.scriptLine;
1208                break;
1209
1210            case cScriptLabel:
1211                result = NextInt(&labelIndex);
1212                if (result == 0) {
1213                    labelIndex--;
1214                    if ((labelIndex < 0) || (labelIndex >= MAXLABELS))
1215                        result = cclErr_BadLabel;
1216                    else {
1217                        if (SV.labels[labelIndex])
1218                            result = cclErr_DuplicateLabel;
1219                        else
1220                            SV.labels[labelIndex] = SV.scriptLine;
1221                    }
1222                }
1223                break;
1224
1225                // The following cases are commands for which we check the syntax
1226                // to verify as much a possible that the parameters are OK
1227            case cExit:
1228                // check for exit result parameter
1229                result = NextInt(&labelIndex);
1230                // check for result in appropriate range?
1231                break;
1232
1233            case cHSReset:
1234                for (i = 0; result == 0 && i < kHSResetParamCount; i++)
1235                    result = NextInt(&labelIndex);
1236                break;
1237
1238            case cAsk:				// check for a integer maskflag value
1239            case cChrDelay:			// check for a integer delay value
1240            case cCommunicatingAt:		// check for a integer modem speed value
1241            case cIfAnswer:			// 				ditto
1242            case cIfOriginate:			// 				ditto
1243            case cJSR:				// 				ditto
1244            case cJump:				// 				mega ditto
1245            case cMatchRead:			// check for a integer timeout value
1246            case cPause:			// check for a integer pause time value
1247            case cSetSpeed:			// check for a integer interface speed value
1248            case cSetTries:			// check for a integer tries value
1249            case cUserHook:			// check for a integer opcode value
1250            case cMonitorLine:			// check for a integer bits mask value
1251            case cDebugLevel:			// check for a integer bits mask value
1252                result = NextInt(&labelIndex);
1253                break;
1254
1255            case cIfStr:
1256                for (i = 0; result == 0 && i < (kIfStrParamCount - 1); i++)
1257                    result = NextInt(&labelIndex);
1258                break;
1259
1260            case cMatchStr:
1261                if (NextInt(&labelIndex) == 0 && labelIndex > maxMatch) {
1262		    result = cclErr_MatchStrIndxErr;
1263		    break;
1264		}
1265		result = NextInt(&labelIndex);
1266                SkipBlanks();
1267                PrepStr(SV.strBuf, NULL, NULL, 1);
1268                //
1269                // Don't allow to match empty string
1270                //
1271                if (SV.strBuf[0] == 0)
1272                    result = cclErr_BadParameter;
1273                break;
1274
1275            case cIfTries:
1276                for (i = 0; result == 0 && i < (kIfTriesParamCount - 1); i++)
1277                    result = NextInt(&labelIndex);
1278                break;
1279
1280            case cLogMsg:			// undocumented.  use cNote.  KW
1281            case cNote:
1282            case cWrite:
1283                SkipBlanks();
1284                PrepStr(SV.strBuf, NULL, NULL, 1);
1285                if( SV.strBuf[0] == 0 )
1286                    result = cclErr_BadParameter;
1287				break;
1288
1289            case cSerReset:
1290                for (i = 0; result == 0 && i < kSerResetParamCount; i++)
1291                    result = NextInt(&labelIndex);
1292                break;
1293
1294            default:
1295                break;
1296        }/* end SWITCH on command type */
1297    }/* end WHILE */
1298
1299    if (result == 0) {
1300        if (SV.originateLine == 0)	result = cclErr_NoOriginateLabel;
1301        else if (SV.answerLine == 0)	result = cclErr_NoAnswerLabel;
1302        else if (SV.hangUpLine == 0)	result = cclErr_NoHangUpLabel;
1303        else SV.scriptLine = 0;
1304
1305        // process the script again to check that all
1306        // destination labels are valid:
1307        while (result == 0 && NextLine()) {
1308            cmd = NextCommand();
1309            switch (cmd) {
1310                case cIfAnswer:
1311                case cIfOriginate:
1312                case cJump:
1313                case cJSR:
1314                    result = NextInt(&labelIndex);
1315                    if (result == 0) {
1316                        labelIndex--;
1317                        if ((labelIndex < 0) || (labelIndex >= MAXLABELS))
1318                            result = cclErr_BadLabel;
1319                        else {
1320                            if (SV.labels[ labelIndex ] == 0)
1321                                result = cclErr_LabelUndefined;
1322                        }
1323                    }
1324                    break;
1325
1326                case cIfStr:
1327                case cIfTries:
1328                case cMatchStr:
1329                    // discard the first parameter
1330                    result = NextInt(&labelIndex);
1331                    if( result == 0 ) {
1332                        result = NextInt(&labelIndex);
1333                        if (result == 0) {
1334                            labelIndex--;
1335                            if ((labelIndex < 0) || (labelIndex >= MAXLABELS))
1336                                result = cclErr_BadLabel;
1337                            else {
1338                                if( SV.labels[ labelIndex ] == 0)
1339                                    result = cclErr_LabelUndefined;
1340                            }
1341                        }
1342                    }
1343                    break;
1344
1345                case cAsk:
1346                    result = NextInt(&labelIndex);		// check for a integer maskflag value
1347                    if (result == 0) {
1348                        SkipBlanks();
1349                        PrepStr(SV.strBuf, 0, 0, 1);	// fetch the message Pascal string
1350
1351                        // this is the optional third parameter, added for ARA 3.0, for Manual Dialing.
1352                        // NextInt() will return cclErr_BadParameter if the third parameter doesn't exist.
1353
1354                        if (NextInt(&labelIndex) == 0) {
1355                            labelIndex--;
1356                            if ((labelIndex < 0) || (labelIndex >= MAXLABELS))
1357                                result = cclErr_BadLabel;
1358                            else {
1359                                if (SV.labels[labelIndex] == 0)
1360                                    result = cclErr_LabelUndefined;
1361                            }
1362                        }
1363                    }
1364                    break;
1365
1366                default:
1367                    break;
1368            }
1369        }/* end WHILE */
1370    }/* end IF no error */
1371    return result;
1372}
1373
1374/* --------------------------------------------------------------------------
1375Advance to the next line of the script.
1376-------------------------------------------------------------------------- */
1377u_int8_t NextLine()
1378{
1379    u_int8_t	*bp;
1380    short	i;
1381
1382    SV.scriptLineIndex = 0;			/* reset line index			*/
1383    SV.scriptLine++;				/* check for out of bounds	*/
1384    if ((SV.scriptLine < 1) || (SV.scriptLine > SV.lineCount))
1385        return 0;
1386
1387    /* update the line ptr	*/
1388    i = SV.indexTable[SV.scriptLine-1];
1389    SV.scriptLinePtr = SV.script + i;
1390
1391    SV.scriptLineSize = 0;
1392    bp = SV.scriptLinePtr;
1393    while ((i++ < SV.scriptSize) &&
1394		   (*bp != chrCR) && (*bp != chrNL) &&
1395           (SV.scriptLineSize < 255)) {
1396        bp++;
1397		SV.scriptLineSize++;
1398    }
1399    return 1;
1400}
1401
1402/* --------------------------------------------------------------------------
1403-------------------------------------------------------------------------- */
1404int equalstring(u_int8_t *s1, u_int8_t *s2)
1405{
1406    int 	l1 = *s1, l2 = *s2;
1407
1408    s1++; s2++;
1409    while (l1 && l2 && (toupper(*s1) == toupper(*s2))) {
1410       l1--; l2--;
1411       s1++; s2++;
1412    }
1413    return ((l1 == 0) && (l2 == 0));
1414}
1415
1416
1417/* --------------------------------------------------------------------------
1418Fetch the next command from the script, and set it up for processing.
1419Return the command number of the command found, or a helpful error
1420code if no command is found
1421-------------------------------------------------------------------------- */
1422int NextCommand()
1423{
1424    u_int8_t	*bp, *cmdStrPtr, cmdFound;
1425    int		cmdIndex, result;
1426    char 	text[256];
1427
1428    result = cComment;				/* assume a blank line		*/
1429    SkipBlanks();				/* skip any initial blanks	*/
1430
1431    bp	= SV.scriptLinePtr + SV.scriptLineIndex;
1432    cmdStrPtr	= &SV.strBuf[1];
1433    SV.strBuf[0] = 0;
1434
1435    while ((SV.scriptLineIndex < SV.scriptLineSize)
1436           && ((*bp != chrSpace) && (*bp != chrHT))) {
1437        SV.scriptLineIndex++;
1438        SV.strBuf[0]++;
1439        *cmdStrPtr++ = *bp++;
1440    }
1441    bcopy(&SV.strBuf[1], &text[0], SV.strBuf[0]);
1442    text[SV.strBuf[0]] = 0;
1443    //    printf("nextcmd = strBuf = %s\n", text);
1444    //syslog(LOG_INFO, "nextcmd = strBuf = %s\n", text);
1445
1446    if (SV.strBuf[0]) {		/* search for this command in command list */
1447        bp		= SV.commands + 2;
1448        cmdIndex	= cFirstCmd;
1449        result		= cNoCmd;			/*assume that we don't have a command*/
1450        cmdFound	= 0;
1451
1452        while (cmdIndex <= cLastCmd && !cmdFound) {
1453            if (cmdIndex == cComment && SV.strBuf[1] == bp[1]) {
1454                result = cComment;
1455                cmdFound = 1;
1456            }
1457            if( equalstring(&SV.strBuf[0], bp)) {
1458                result = cmdIndex;
1459                cmdFound = 1;
1460            }
1461            else {
1462                bp += *bp + 1;
1463                cmdIndex++;
1464            }
1465        }
1466    }
1467
1468    return result;
1469}
1470
1471/* --------------------------------------------------------------------------
1472Parse and prepare srcStr (from srcNdx) and place result in destStr;
1473srcStr and destStr are both pascal strings (Str255).
1474
1475If the string is derived by substituting for a varString, return true
1476in "isVarString" and the string index in "varIndex.
1477-------------------------------------------------------------------------- */
1478void PrepStr(u_int8_t *destStr, u_int32_t *isVarString, u_int32_t *varIndex, int varSubstitution)
1479{
1480    u_int8_t	*s, *d, *maskPtr;
1481    u_int16_t	srcStrIndex;
1482    u_int8_t	done, chDelimiter, escChar, dstStrLen, centerDot = '�';
1483
1484    //
1485    // Assume the string is not a varString.
1486    //
1487    if ( isVarString != NULL )
1488        *isVarString = 0;
1489
1490	// make sure we don't walk off the end of the line
1491	if (SV.scriptLineIndex > SV.scriptLineSize) {
1492		destStr[0] = '\0';
1493		return;
1494	}
1495
1496    srcStrIndex	= SV.scriptLineIndex;
1497    s		= SV.scriptLinePtr + srcStrIndex;
1498
1499    //
1500    // determine string terminators
1501    // default is space, tab, comma, semicolon
1502    //
1503    chDelimiter = 0;
1504    if ((*s == chrDblQuote) || (*s == chrQuote)) {
1505        chDelimiter = *s++;
1506        srcStrIndex++;
1507    }
1508
1509    d		= destStr + 1;
1510    dstStrLen	= 0;
1511    done	= 0;
1512
1513    while ((srcStrIndex < SV.scriptLineSize) && !done) {
1514
1515        // copy & prepare the string
1516        maskPtr = 0;
1517        switch (*s) {
1518            // special delimiters
1519            case chrDblQuote:
1520            case chrQuote:
1521                if (chDelimiter == *s)
1522                    done = 1;
1523                else {
1524                    srcStrIndex++;
1525                    dstStrLen++;
1526                    *d++ = *s++;
1527                }
1528                break;
1529
1530                // default delimiters
1531            case chrHT:
1532            case chrSpace:
1533            case chrComma:
1534            case chrSemiColon:
1535                if (!chDelimiter)
1536                    done = 1;
1537                else {
1538                    srcStrIndex++;
1539                    dstStrLen++;
1540                    *d++ = *s++;
1541                }
1542                break;
1543
1544            // copy escape character into the destStr
1545            case chrBackSlash:
1546                s++;
1547                if ((*s == chrBackSlash) || (*s == chrCaret)) {
1548                    srcStrIndex += 2;
1549                    *d++ = *s++;
1550                }
1551                else if (*s == 'x') {
1552                    srcStrIndex += 4;
1553                    s++;
1554                    escChar = ((*s - ((*s <= '9') ? '0' : ('A' - 10))) * 16);
1555					s++;
1556					escChar += (*s - ((*s <= '9') ? '0' : ('A' - 10)));
1557					s++;
1558                    *d++ = escChar;
1559                }
1560                else {
1561                    srcStrIndex += 3;
1562                    escChar = ((*s++ - '0') * 10);
1563                    escChar += (*s++ - '0');
1564                    *d++ = escChar;
1565                }
1566                dstStrLen++;
1567                break;
1568
1569                // copy the varString into the destStr
1570            case chrCaret:
1571            {
1572                u_int8_t	vs, *vsp;
1573                int 	i;
1574
1575                if (varSubstitution == 0) {
1576                    srcStrIndex++;
1577                    dstStrLen++;
1578                    *d++ = *s++;
1579                    break;
1580                }
1581
1582                s++;
1583                srcStrIndex += 2;
1584
1585                switch (vs = *s++) {
1586                    case '*':		vs = vsAsk;		break;
1587                    case 'u': case 'U':
1588						vs = vsUserName;
1589						break;
1590                    case 'p': case 'P':	vs = vsPassWord;	break;
1591                    default: {
1592                        vs -= '0';
1593                        if (*s >= '0' && *s <= '9') {
1594                            srcStrIndex++;
1595                            vs = 10 * vs + *s++ - '0';
1596                        }
1597                        break;
1598                    }
1599                }
1600
1601                if (isVarString)
1602                    *isVarString = 1;
1603
1604                if (varIndex)
1605                    *varIndex = vs;
1606
1607                if (vsp = GetVarString(vs)) {
1608                    if (SV.logMaskOn && vs == (SV.maskStringId - 1)  && SV.maskStart) {
1609                        maskPtr = d + SV.maskStart - 1;
1610                    }
1611
1612                    for (i = 1; i <= *vsp; i++, dstStrLen++)
1613                        *d++ = vsp[i];
1614
1615                    if (maskPtr) {
1616                        // %% centerDot = GetPasswordBulletChar();
1617                        for (i = SV.maskStop - SV.maskStart; i >= 0; i--)
1618                            *maskPtr++ = centerDot;
1619                    }
1620                }
1621                break;
1622            }
1623
1624            // copy srcStr byte into the destStr
1625            default:
1626                srcStrIndex++;
1627                dstStrLen++;
1628                *d++ = *s++;
1629                break;
1630        }
1631    }
1632
1633    *destStr = dstStrLen;			// pascal string - set length
1634    SV.scriptLineIndex = srcStrIndex + 1;	// skip over the string terminator
1635
1636}
1637
1638/* --------------------------------------------------------------------------
1639parse a string and substitute variable with actual values
1640-------------------------------------------------------------------------- */
1641void varSubstitution(u_int8_t *src, u_int8_t *dst, int dstmaxlen)
1642{
1643    u_int8_t	*s = src + 1, *d = dst + 1;
1644    u_int16_t	srclen = src[0];
1645    u_int8_t	len = 0, dstlen = 0;
1646
1647    while ((len < srclen) && (dstlen < dstmaxlen)) {
1648        // copy & prepare the string
1649        switch (*s) {
1650
1651            // copy the varString into the destStr
1652            case chrCaret:
1653            {
1654                u_int8_t	vs, *vsp;
1655                int 	i;
1656
1657                s++;
1658                len += 2;
1659
1660                switch (vs = *s++) {
1661                    case '*':		vs = vsAsk;		break;
1662                    case 'u': case 'U':	vs = vsUserName;	break;
1663                    case 'p': case 'P':	vs = vsPassWord;	break;
1664                    default: {
1665                        vs -= '0';
1666                        if (*s >= '0' && *s <= '9') {
1667                            len++;
1668                            vs = 10 * vs + *s++ - '0';
1669                        }
1670                        break;
1671                    }
1672                }
1673                if (vsp = GetVarString(vs)) {
1674                    for (i = 1; (i <= *vsp) && (dstlen < dstmaxlen); i++, dstlen++)
1675                        *d++ = vsp[i];
1676                }
1677                break;
1678            }
1679
1680            // copy srcStr byte into the dst
1681            default:
1682				len++;
1683                dstlen++;
1684                *d++ = *s++;
1685                break;
1686        }
1687    }
1688
1689    *dst = dstlen;			// pascal string - set length
1690}
1691
1692/* --------------------------------------------------------------------------
1693Skip over white space (spaces and tabs)
1694-------------------------------------------------------------------------- */
1695void SkipBlanks()
1696{
1697    u_int8_t	*s;
1698
1699    s = SV.scriptLinePtr + SV.scriptLineIndex;
1700    while ((SV.scriptLineIndex < SV.scriptLineSize)
1701           && ((*s == chrSpace) || (*s == chrHT))) {
1702        s++;
1703        SV.scriptLineIndex++;
1704    }
1705}
1706
1707/* --------------------------------------------------------------------------
1708Read the next text from the command line and convert the ascii to
1709an int, and set theIntPtr to the result.
1710If there is no int to be read, return cclErr_BadParameter.
1711If everything is OK, return noErr
1712-------------------------------------------------------------------------- */
1713int NextInt(u_int32_t *theIntPtr)
1714{
1715    int		sign;
1716    u_int8_t	*intStrPtr, intStrNdx, intStrLen;
1717    u_int8_t	intStr[256];
1718	u_int32_t	isVarString = 0;
1719
1720    /* skip over blanks in script line, and pull out the number string	*/
1721    SkipBlanks();
1722    PrepStr( intStr, &isVarString, NULL, 1);
1723
1724    *theIntPtr = 0;
1725    sign = 1;
1726    intStrLen = *intStr;
1727    intStrPtr = intStr + 1;
1728    if( intStrLen == 0 )
1729        return cclErr_BadParameter;
1730	// if it's a variable that is null (gNullString is a pstring = " "),
1731	// substitute zero instead of failing later
1732	if (isVarString && (intStrLen == 1) && *intStrPtr == ' ')
1733		*intStrPtr = '0';
1734
1735    /* check for sign operator	*/
1736    intStrNdx = 1;
1737    if (*intStrPtr == '-') {
1738        sign = -1;
1739        intStrNdx++;
1740        intStrPtr++;
1741    }
1742    if (intStrNdx > intStrLen)
1743        return cclErr_BadParameter;
1744
1745    /* parse out the number	*/
1746    while ((intStrNdx++ <= intStrLen) && (*intStrPtr >= '0') && (*intStrPtr <= '9'))
1747           *theIntPtr = (*theIntPtr * 10) + (*intStrPtr++ - '0');
1748
1749    if (intStrPtr == (intStr + 1))	// the string did not contain numbers
1750        return cclErr_BadParameter;
1751
1752    *theIntPtr *= sign;				// set appropriate sign
1753    return 0;
1754}
1755
1756/* --------------------------------------------------------------------------
1757Run the script, one line at a time, by reading the command and
1758dispatching to the proper routine. This continues until some command
1759defers the script execution. This could be due to encountering
1760the end of the script, or waiting for an asynchronous command to complete.
1761-------------------------------------------------------------------------- */
1762void RunScript()
1763{
1764    u_int8_t	running, cmd;
1765    int         code, result = 0;
1766    u_int32_t   i, lval, jumpLabel;
1767
1768    if (SV.scriptLine == 0) {			// no entry point, so terminate the script.
1769        SV.ctlFlags &= ~cclPlaying;				// don't play this script.
1770        SV.theAbortErr = cclErr_EndOfScriptErr;	// for WrapScript()->ScriptComplete().
1771        terminate(cclErr_EndOfScriptErr);	// a script syntax error was detected
1772        return;
1773    }
1774
1775    SV.ctlFlags |= cclPlaying;		// turn on the cclPlaying flag
1776    running = 1;			// keep running until SerReset or other interruption
1777    while (running) {			// find the next script line
1778        if (!NextLine()) {		// no more lines in the script, so we've hit the end without an exit.
1779
1780            SV.ctlFlags &= ~cclPlaying;			// don't play this script.
1781            SV.theAbortErr = cclErr_EndOfScriptErr;	// for WrapScript()->ScriptComplete().
1782            terminate(cclErr_EndOfScriptErr);	// a script syntax error was detected
1783            return;
1784        }
1785
1786        /* process script line	*/
1787        cmd = NextCommand();			// get the next CCL Command from the script.
1788		switch (cmd) {
1789            case cAsk:
1790                running = !Ask();		// wait until User data arrives in ReceiveDataFromAbove().
1791                break;
1792
1793            case cChrDelay:
1794                NextInt( &i );			// delay is specified in tenths of a second
1795               // if (i > 0)		// CA : Why is it impossible to reset delay ?
1796                SV.chrDelayValue = i * 100;
1797                break;
1798
1799            case cCommunicatingAt:
1800                CommunicatingAt();		// tell upstairs what our modulation speed is
1801                break;
1802
1803            case cDecTries:
1804                SV.loopCounter--;		// decrement loop counter
1805                break;
1806
1807            case cDTRClear:
1808                DTRCommand(DTR_CLEAR);		// Issue the DTR Clear command
1809                break;
1810
1811            case cDTRSet:
1812                DTRCommand(DTR_SET);		// Issue the DTR Set command
1813                break;
1814
1815            case cExit:
1816                running = 0;
1817                SV.ctlFlags &= ~cclPlaying;
1818
1819                NextInt((u_int32_t*)&code);		// fetch the Exit code
1820				SV.theAbortErr = code;			// not currently used
1821
1822				Note();							// handle optional string
1823
1824				terminate(code);	// send an ARA_Notify message upstream.
1825                break;
1826
1827            case cFlush:
1828                Flush();
1829                break;
1830
1831            case cHSReset:
1832                HSReset();				// Configure the serial driver handshake options
1833                break;
1834
1835            case cIfAnswer:
1836                if (SV.ctlFlags & cclAnswerMode) {
1837                    NextInt(&i);
1838                    SV.scriptLine = SV.labels[i - 1];
1839                }
1840                break;
1841
1842            case cIfOriginate:
1843                if (SV.ctlFlags & cclOriginateMode) {
1844                    NextInt(&i);
1845                    SV.scriptLine = SV.labels[i - 1];
1846                }
1847                break;
1848
1849            case cIfStr:
1850                // IfStr() will return either 0 if the comparison fails,
1851                // or the label to jump to if the comparison succeeds.
1852                jumpLabel = IfStr();
1853                if (jumpLabel > 0)
1854                    SV.scriptLine = jumpLabel;
1855                break;
1856
1857            case cIfTries:
1858                NextInt(&i);
1859                if (SV.loopCounter >= i) {
1860                    NextInt(&i);
1861                    SV.scriptLine = SV.labels[i - 1];
1862                }
1863                break;
1864
1865            case cIncTries:
1866                SV.loopCounter++;					// increment the loop counter
1867                break;
1868
1869            case cJump:
1870               NextInt(&i);
1871                SV.scriptLine = SV.labels[i - 1];
1872                break;
1873
1874            case cJSR:
1875                if (SV.topOfStack == 0) {
1876                    running = 0;
1877                    SV.ctlFlags &= ~cclPlaying;
1878                    SV.theAbortErr = cclErr_SubroutineOverFlow;	// for WrapScript()->ScriptComplete().
1879                    terminate(cclErr_SubroutineOverFlow);
1880                }
1881                else {
1882                    SV.stack[--SV.topOfStack] = SV.scriptLine;	// save return line
1883                    NextInt(&i);
1884                    SV.scriptLine = SV.labels[i - 1];
1885                }
1886                break;
1887
1888            case cLogMsg:			// undocumented.  use cNote.  KW
1889            case cNote:
1890                Note();				// post a Note to the Status window and/or Log File.
1891                break;
1892
1893            case cMatchClr:
1894                MatchClr();			// clear the match buffer.
1895                break;
1896
1897            case cMatchRead:
1898                SV.ctlFlags |= cclMatchPending;	// any Serial data is for CCL.
1899                for(i = 0; i < maxMatch; i++)	{	// reset match string indices
1900                    SV.matchStr[i].matchStrIndex	= 0;
1901                    SV.matchStr[i].inVarStr		= 0;
1902                }
1903
1904                NextInt(&i);	// get the timeout value
1905                if( i > 0 ) {
1906                    // post read to serial driver and set timer:
1907                    running = 0;	// stop running script til match or timeout
1908                    ScheduleTimer(kMatchReadTimer, i * 100);
1909                    StartRead();
1910                }
1911                break;
1912
1913            case cMatchStr:
1914                result = MatchStr();		// add a string to the match buffer
1915                if (result) {			// bad command and/or matchstr index
1916                    running = 0;
1917                    SV.ctlFlags &= ~cclPlaying;
1918                    SV.theAbortErr = result;	// for WrapScript()->ScriptComplete().
1919                    terminate(result);
1920                }
1921                break;
1922
1923            case cPause:
1924                NextInt(&i);
1925                if (i > 0) {
1926                    lval = SV.pauseTimer = i;
1927                    lval *= 100; // get it in milliseconds
1928                    usleep(lval * 1000);
1929                }
1930                break;
1931
1932            case cReturn:
1933                if (SV.topOfStack == cclNestingLimit) {
1934                    running = 0;
1935                    SV.ctlFlags &= ~cclPlaying;
1936                    SV.theAbortErr = cclErr_SubroutineOverFlow;	// for WrapScript()->ScriptComplete().
1937                    terminate(cclErr_SubroutineOverFlow);
1938                }
1939                else
1940                    SV.scriptLine = SV.stack[SV.topOfStack++];
1941                break;
1942
1943            case cLBreak:
1944                Break(LONGBREAK);
1945                break;
1946
1947            case cSBreak:
1948                Break(SHORTBREAK);
1949                break;
1950
1951            case cMonitorLine:
1952                MonitorLine();
1953                break;
1954
1955            case cDebugLevel:
1956                DebugLevel();
1957                break;
1958
1959            case cSerReset:
1960                SerReset();
1961                break;
1962
1963            case cSetSpeed:
1964                SetSpeed();
1965                break;
1966
1967            case cSetTries:
1968                NextInt(&i);
1969                SV.loopCounter = i;
1970                break;
1971
1972            case cUserHook:
1973                UserHook();			// get an event and pass it up to the Client.
1974                break;
1975
1976            case cWrite:
1977                running = Write();
1978                break;
1979
1980            default:		// "!", "@CCLSCRIPT", "@ORIGINATE", "@ANSWER", "@HANGUP", "@LABEL" (cmd 1-6).
1981            {
1982				break;
1983			}
1984
1985        }/* end SWITCH */
1986    }/* end WHILE running */
1987}
1988
1989/* --------------------------------------------------------------------------
1990Copy the match string into the approptiate MatchStr variable
1991-------------------------------------------------------------------------- */
1992int MatchStr()
1993{
1994    u_int32_t		matchIndex, i;
1995    TPMatchStrInfo	matchInfo;
1996    int			result;
1997
1998    result = NextInt(&matchIndex);			// Get the matchStr index
1999    if (result == 0) {
2000        matchIndex--;
2001        if ((matchIndex < 0) || (matchIndex >= maxMatch)) {	// index out of bounds, abort the script play
2002            return cclErr_MatchStrIndxErr;
2003        }
2004
2005        matchInfo = &SV.matchStr[matchIndex];
2006        // get script line index from specified label
2007        result = NextInt(&i);
2008        if (result == 0) {
2009            matchInfo->matchLine = SV.labels[i - 1];
2010
2011            SkipBlanks();
2012
2013            /* determine string terminators - default is space, tab, comma, semicolon */
2014            if ((SV.scriptLinePtr[SV.scriptLineIndex] == chrDblQuote)
2015                || (SV.scriptLinePtr[SV.scriptLineIndex] == chrQuote)) {
2016                matchInfo->delimiterChar = SV.scriptLinePtr[SV.scriptLineIndex];
2017                SV.scriptLineIndex++;
2018            }
2019            else
2020                matchInfo->delimiterChar = 0;
2021            matchInfo->matchStr = &SV.scriptLinePtr[SV.scriptLineIndex];
2022        }
2023    }
2024
2025    return result;
2026}
2027
2028/* --------------------------------------------------------------------------
2029Clear all the match strings
2030-------------------------------------------------------------------------- */
2031void MatchClr()
2032{
2033    int			matchIndex;
2034    TPMatchStrInfo	matchInfo;
2035
2036    for (matchIndex = 0; matchIndex < maxMatch; matchIndex++) {
2037        matchInfo = &SV.matchStr[matchIndex];
2038        matchInfo->matchStr		= 0;
2039        matchInfo->matchStrIndex	= 0;
2040        matchInfo->delimiterChar	= 0;
2041        matchInfo->varStr		= 0;
2042        matchInfo->inVarStr		= 0;
2043        matchInfo->varStrIndex		= 0;
2044        matchInfo->matchLine		= 0;
2045    }
2046}
2047
2048/* --------------------------------------------------------------------------
2049Search each match string, at it's current index, for a match with
2050newChar.  If newChar matches, andvance the strings index and see if
2051if the whole string has been matched.  Else, reset the match string's
2052index.  Returns which matchstring matched, if any
2053-------------------------------------------------------------------------- */
2054int MatchFind(u_int8_t newChar)
2055{
2056    int			i, matchFound;
2057    TPMatchStrInfo	matchInfo = NULL;
2058    char 		text[256];
2059    u_int8_t		matchStrChar, c;
2060
2061    matchFound = 0;								// assume no match found
2062    newChar &= 0x7f;							// some PADs have bogus high bit
2063    for (i = 0; i < maxMatch && !matchFound; i++) {
2064        matchInfo = &SV.matchStr[i];
2065       if (matchInfo->matchStr) {		// we have a matchStr, so test it
2066            if (matchInfo->inVarStr) {
2067                matchStrChar = matchInfo->varStr[matchInfo->varStrIndex];
2068                matchInfo->varStrIndex++;
2069                if (matchStrChar == chrBackSlash) {
2070                    matchStrChar = matchInfo->varStr[matchInfo->varStrIndex];
2071                    matchInfo->varStrIndex++;
2072                    if ((matchStrChar !=  chrBackSlash)
2073                        && (matchStrChar != chrCaret)) {
2074                        if (matchStrChar == 'x') {
2075                            c = matchInfo->matchStr[matchInfo->varStrIndex++];
2076                            matchStrChar = ((c - ((c <= '9') ? '0' : ('A' - 10))) * 16);
2077                            c = matchInfo->matchStr[matchInfo->varStrIndex++];
2078                            matchStrChar += (c - ((c <= '9') ? '0' : ('A' - 10)));
2079                       }
2080                       else {
2081                            matchStrChar = (matchStrChar - 0x30) * 10;
2082                            matchStrChar +=  matchInfo->varStr[matchInfo->varStrIndex] - 0x30;
2083                            matchInfo->varStrIndex++;
2084                        }
2085                    }
2086                }
2087                if (matchInfo->varStrIndex == (matchInfo->varStrSize + 1))
2088                    matchInfo->inVarStr = 0;
2089            }
2090            else {
2091                matchStrChar = matchInfo->matchStr[matchInfo->matchStrIndex];
2092                matchInfo->matchStrIndex++;			// move index to next char
2093                if (matchStrChar == chrBackSlash) {
2094                    matchStrChar = matchInfo->matchStr[matchInfo->matchStrIndex];
2095                    matchInfo->matchStrIndex++;		// update index for next char
2096                    if ((matchStrChar != chrBackSlash)
2097                        && (matchStrChar != chrCaret)) {
2098                        if (matchStrChar == 'x') {
2099                            c = matchInfo->matchStr[matchInfo->matchStrIndex++];
2100                            matchStrChar = ((c - ((c <= '9') ? '0' : ('A' - 10))) * 16);
2101                            c = matchInfo->matchStr[matchInfo->matchStrIndex++];
2102                            matchStrChar += (c - ((c <= '9') ? '0' : ('A' - 10)));
2103                       }
2104                       else {
2105                            matchStrChar = (matchStrChar - 0x30) * 10;
2106                            matchStrChar += matchInfo->matchStr[matchInfo->matchStrIndex] - 0x30;
2107                            matchInfo->matchStrIndex++;
2108                        }
2109                    }
2110                }
2111                else if (matchStrChar == chrCaret) {
2112                    u_int8_t	vs;
2113
2114                   matchStrChar = matchInfo->matchStr[matchInfo->matchStrIndex];
2115                    matchInfo->matchStrIndex++;		// skip past var string index
2116                    if (matchStrChar == '*')
2117                        vs = vsAsk;
2118                    else if (matchStrChar == 'u' ||  matchStrChar == 'U')
2119                        vs = vsUserName;
2120                    else if (matchStrChar == 'p' ||  matchStrChar == 'P')
2121                        vs = vsPassWord;
2122                    else {
2123                        vs = matchStrChar - '0';
2124                        matchStrChar = matchInfo->matchStr[matchInfo->matchStrIndex];
2125                        if (matchStrChar >= '0' && matchStrChar <= '9') {
2126                            matchInfo->matchStrIndex++;		// skip past var string index
2127                            vs = 10 * vs + matchStrChar - '0';
2128                        }
2129                    }
2130                    matchInfo->varStr = GetVarString(vs);
2131                    if (matchInfo->varStr) {
2132                        matchStrChar = matchInfo->varStr[1];
2133                        matchInfo->varStrIndex	= 2;
2134                        matchInfo->inVarStr	= 1;
2135                        matchInfo->varStrSize	= matchInfo->varStr[0];
2136                        if( (matchInfo->varStrSize + 1) == matchInfo->varStrIndex)
2137                            // we are done with the var string, clear inVarStr flag
2138                            matchInfo->inVarStr = 0;
2139                    }
2140                    else {
2141                        matchStrChar = matchInfo->matchStr[matchInfo->matchStrIndex];
2142                        matchInfo->matchStrIndex++;
2143                    }
2144                }
2145            }
2146
2147            //printf("MatchFind, expect = %d, newchar = %d\n", matchStrChar, newChar);
2148            //syslog(LOG_INFO, "MatchFind, expect = 0x%x '%c', newchar = 0x%x '%c'\n", matchStrChar, matchStrChar, newChar, newChar);
2149            if (newChar == matchStrChar) {		// check to see if whole string matched
2150                if (!matchInfo->inVarStr)
2151                    switch (matchInfo->matchStr[ matchInfo->matchStrIndex ]) {
2152                        // special delimiters
2153                        case chrDblQuote:
2154                        case chrQuote:
2155                            if (matchInfo->delimiterChar)
2156                                matchFound = i + 1;
2157                            break;
2158
2159                            // default delimiters
2160                        case chrHT:
2161                        case chrSpace:
2162                        case chrComma:
2163                        case chrSemiColon:
2164                            if (!matchInfo->delimiterChar)
2165                                matchFound = i + 1;
2166                            break;
2167
2168                        case chrCR:
2169                        case chrNL:
2170                            matchFound = i + 1;
2171                            break;
2172                    }/* end SWITCH */
2173            }
2174            else {		// newChar does not match, reset matchInfo
2175                matchInfo->matchStrIndex = 0;
2176                matchInfo->inVarStr = 0;
2177                matchInfo->varStr = 0;
2178            }
2179        }
2180    }
2181
2182    if (matchFound) {
2183        int x;
2184
2185        if (matchInfo->inVarStr) {
2186            for (x = 0; x < matchInfo->varStrIndex; x++)
2187                VerboseBuffer[x+1] = matchInfo->varStr[x];
2188            VerboseBuffer[0] = x;
2189        }
2190        else {
2191            for (x = 0; x < matchInfo->matchStrIndex; x++)
2192               VerboseBuffer[x+1] = matchInfo->matchStr[x];
2193            VerboseBuffer[0] = x;
2194        }
2195
2196        //
2197        // Use the kUserMsgFAny flag. That allows the message
2198        // resource to determine the destination and log level.
2199        //
2200
2201        bcopy(&VerboseBuffer[1], &text[0], VerboseBuffer[0]);
2202        text[VerboseBuffer[0]] = 0;
2203        if (verbose) {
2204			sLog("CCLMatched : %s", text);
2205        }
2206    }
2207
2208    return matchFound;
2209}
2210
2211/* --------------------------------------------------------------------------
2212-------------------------------------------------------------------------- */
2213void Break(u_int32_t len)
2214{
2215    /*
2216     Man pages :
2217     The tcsendbreak function transmits a continuous stream of zero-valued
2218    bits for four-tenths of a second to the terminal referenced by fd. The
2219    len parameter is ignored in this implementation. */
2220    /* we need to fix the question of Short break vs. Long Break */
2221
2222    tcsendbreak(outfd, len);
2223}
2224
2225/* --------------------------------------------------------------------------
2226A macro to return a Boolean result for a given baud number. Only those
2227listed are valid
2228-------------------------------------------------------------------------- */
2229#define IsValidBaudRate(a)		\
2230        (	((a) == 300)	||		\
2231                ((a) == 600)	||		\
2232                ((a) == 1200)	||		\
2233                ((a) == 1800)	||		\
2234                ((a) == 2400)	||		\
2235                ((a) == 3600)	||		\
2236                ((a) == 4800)	||		\
2237                ((a) == 7200)	||		\
2238                ((a) == 9600)	||		\
2239                ((a) == 14400)	||		\
2240                ((a) == 19200)  ||      \
2241                ((a) == 28800)  ||      \
2242                ((a) == 38400)  ||      \
2243                ((a) == 57600)  ||      \
2244                ((a) == 115200) ||      \
2245                ((a) == 230400) ||      \
2246                ((a) == 460800) ||      \
2247                ((a) == 921600))
2248
2249/* --------------------------------------------------------------------------
2250Reset the Serial driver transmission rate, parity, number of data bits,
2251and number of stop bits as specified on the command line
2252-------------------------------------------------------------------------- */
2253void SerReset(void)
2254{
2255    struct termios 	tios;
2256    u_int32_t 		temp;
2257
2258    if (tcgetattr(infd, &tios) < 0)
2259        return;
2260
2261    // Get the baud rate from the script and filter out invalid values.
2262    NextInt(&temp);
2263    SV.serialSpeed	= IsValidBaudRate(temp) ? temp : 9600;
2264    // sLog("SerReset: setting serial speed to %d", SV.serialSpeed);
2265    cfsetispeed(&tios, SV.serialSpeed);
2266    cfsetospeed(&tios, SV.serialSpeed);
2267
2268    // Get the parity setting from the script;
2269    NextInt(&temp);
2270    switch (temp) {
2271        case 1:
2272            tios.c_cflag |= PARENB + PARODD;     // parity enable + odd parity
2273            break;
2274        case 2:
2275            tios.c_cflag |= PARENB;
2276            tios.c_cflag &= ~PARODD;     // parity enable + even parity
2277            break;
2278        default:
2279            tios.c_cflag &= ~(PARENB + PARODD);     // parity enable + even parity
2280            break;
2281    }
2282
2283    // Get the data bits setting from the script.
2284    tios.c_cflag &= ~CSIZE;     // parity enable + even parity
2285    NextInt(&temp);
2286    switch (temp) {
2287        case 5:
2288            tios.c_cflag |= CS5;     // 5 data bits
2289            break;
2290        case 6:
2291            tios.c_cflag |= CS6;     // 6 data bits
2292            break;
2293        case 7:
2294            tios.c_cflag |= CS7;     // 7 data bits
2295            break;
2296        default:
2297            tios.c_cflag |= CS8;     // 8 data bits
2298    }
2299
2300    // Get the stop bits setting...
2301    NextInt(&temp);
2302    switch (temp) {
2303        case 2:
2304            tios.c_cflag |= CSTOPB;     // 2 stop bits
2305            break;
2306        default:
2307            tios.c_cflag &= ~CSTOPB;     // 1 stop bit
2308            break;
2309    }
2310
2311    // set the same settings for inout and output fd
2312    tcsetattr(infd, TCSAFLUSH, &tios);
2313    tcsetattr(outfd, TCSAFLUSH, &tios);
2314}
2315
2316/* --------------------------------------------------------------------------
2317Set the CLOCAL flag to receive or not SIGHUP when the line drops
2318-------------------------------------------------------------------------- */
2319void MonitorLine()
2320{
2321    struct termios 	tios;
2322    u_int32_t 		temp;
2323
2324    if (tcgetattr(infd, &tios) < 0)
2325        return;
2326
2327    // get the value.
2328    // bit mask : 1st flag -> on/off line drop
2329    NextInt(&temp);
2330    if (temp)
2331        tios.c_cflag &= ~CLOCAL;
2332    else
2333        tios.c_cflag |= CLOCAL;
2334
2335    // set the same settings for inout and output fd
2336    tcsetattr(infd, TCSAFLUSH, &tios);
2337    tcsetattr(outfd, TCSAFLUSH, &tios);
2338}
2339
2340/* --------------------------------------------------------------------------
2341Set the debug level for the script
2342-------------------------------------------------------------------------- */
2343void DebugLevel()
2344{
2345    u_int32_t 		temp;
2346
2347    // get the value.
2348    // bit mask : 1st flag -> print incoming data
2349    NextInt(&temp);
2350
2351    debuglevel = temp;
2352}
2353
2354/* --------------------------------------------------------------------------
2355*	Get the serial speed from the CCL script and send it down as an
2356*	XTI option to the layer below.
2357*
2358*	Called by TCCLScript::RunScript().
2359*
2360*	NOTE on OT handling of speeds:
2361*	For speeds that return false from the IsValidBaudRate macro, the OT
2362*	serial module will usually return T_PARTSUCCESS and return the value
2363*	it likes closest to the requested value.  For OT 1.1 the exceptions are:
2364*		14400: sets rate to 19,200, returns T_PARTSUCCESS
2365*		any speed btw 100k and 230k: sets rate to 19,200, returns T_FAILURE
2366*		230,400: T_SUCCESS
2367*		any speed other speed btw 230k and 235,930, sets rate to 230,400 and
2368*			returns T_PARTSUCCESS
2369*		any speed > 235,930, set rate to 19,200 and returns T_FAILURE
2370-------------------------------------------------------------------------- */
2371void SetSpeed(void)
2372{
2373    u_int32_t 		temp;
2374    struct termios 	tios;
2375
2376    NextInt(&temp);
2377    SV.serialSpeed = IsValidBaudRate(temp) ? temp : 2400;
2378
2379    if (tcgetattr(infd, &tios) >= 0) {
2380        cfsetispeed(&tios, SV.serialSpeed);
2381        cfsetospeed(&tios, SV.serialSpeed);
2382        tcsetattr(infd, TCSAFLUSH, &tios);
2383        tcsetattr(outfd, TCSAFLUSH, &tios);
2384    }
2385}
2386
2387/* -----------------------------------------------------------------------------
2388----------------------------------------------------------------------------- */
2389CFStringRef copyUserLocalizedString(CFBundleRef bundle,
2390    CFStringRef key, CFStringRef value, CFArrayRef userLanguages)
2391{
2392    CFStringRef 	result = NULL, errStr= NULL;
2393    CFDictionaryRef 	stringTable;
2394    CFDataRef 		tableData;
2395    SInt32 		errCode;
2396    CFURLRef 		tableURL;
2397    CFArrayRef		locArray, prefArray;
2398
2399    if (userLanguages == NULL)
2400        return CFBundleCopyLocalizedString(bundle, key, value, NULL);
2401
2402    if (key == NULL)
2403        return (value ? CFRetain(value) : CFRetain(CFSTR("")));
2404
2405    locArray = CFBundleCopyBundleLocalizations(bundle);
2406    if (locArray) {
2407        prefArray = CFBundleCopyLocalizationsForPreferences(locArray, userLanguages);
2408        if (prefArray) {
2409            if (CFArrayGetCount(prefArray)) {
2410                tableURL = CFBundleCopyResourceURLForLocalization(bundle, CFSTR("Localizable"), CFSTR("strings"), NULL,
2411                                    CFArrayGetValueAtIndex(prefArray, 0));
2412                if (tableURL) {
2413                    if (CFURLCreateDataAndPropertiesFromResource(NULL, tableURL, &tableData, NULL, NULL, &errCode)) {
2414                        stringTable = CFPropertyListCreateFromXMLData(NULL, tableData, kCFPropertyListImmutable, &errStr);
2415                        if (errStr)
2416                            CFRelease(errStr);
2417                        if (stringTable) {
2418                            result = CFDictionaryGetValue(stringTable, key);
2419                            if (result)
2420                                CFRetain(result);
2421                            CFRelease(stringTable);
2422                        }
2423                        CFRelease(tableData);
2424                    }
2425                    CFRelease(tableURL);
2426                }
2427            }
2428            CFRelease(prefArray);
2429        }
2430        CFRelease(locArray);
2431    }
2432
2433    if (result == NULL)
2434        result = (value && !CFEqual(value, CFSTR(""))) ?  CFRetain(value) : CFRetain(key);
2435
2436    return result;
2437}
2438
2439/* --------------------------------------------------------------------------
2440-------------------------------------------------------------------------- */
2441bool localizeStringWithBundle(u_char* inString, u_char* outString, CFIndex outSize, CFURLRef curURL)
2442{
2443	bool retVal= false;
2444	if(curURL && inString && outString)
2445	{
2446		CFStringRef			ref, loggedInUser, msg;
2447		CFPropertyListRef	langRef;
2448		CFBundleRef			bdl;
2449
2450		loggedInUser = SCDynamicStoreCopyConsoleUser(NULL, 0, 0);
2451		if (loggedInUser)
2452		{
2453			CFPreferencesSynchronize(kCFPreferencesAnyApplication, loggedInUser, kCFPreferencesAnyHost);
2454			langRef = CFPreferencesCopyValue(CFSTR("AppleLanguages"), kCFPreferencesAnyApplication,
2455							loggedInUser, kCFPreferencesAnyHost);
2456			if (langRef)
2457			{
2458				ref = CFStringCreateWithPascalString(NULL, inString, kCFStringEncodingUTF8);
2459				if (ref)
2460				{
2461					bdl = CFBundleCreate(0, curURL);
2462					if (bdl)
2463					{
2464						msg = copyUserLocalizedString(bdl, ref, ref, langRef);
2465						if (msg)
2466						{
2467							retVal= CFStringGetPascalString(msg, outString, outSize, kCFStringEncodingUTF8);
2468							CFRelease(msg);
2469						}
2470						CFRelease(bdl);
2471					}
2472					CFRelease(ref);
2473				}
2474				CFRelease(langRef);
2475			}
2476			CFRelease(loggedInUser);
2477		}
2478	}
2479	return retVal;
2480}
2481
2482/* --------------------------------------------------------------------------
2483-------------------------------------------------------------------------- */
2484void localizeString(u_char* inString, u_char* outString, CFIndex outSize)
2485{
2486	bool success = true;
2487	outString[0]= 0;
2488
2489	if(!localizeStringWithBundle(inString, outString, outSize, cclBundleURL))
2490	{
2491		if(!localizeStringWithBundle(inString, outString, outSize, appBundleURL))
2492		{
2493			success = localizeStringWithBundle(inString, outString, outSize, localBundleURL);
2494		}
2495	}
2496
2497	// if we couldn't localize, just copy the non-localized version
2498	// (the routines above used to do that, but they're busted)
2499	if (!success)
2500		bcopy(inString, outString, inString[0]+1);
2501}
2502
2503#pragma mark -
2504
2505/* --------------------------------------------------------------------------
2506pass a string up to the Client, along with where it should be displayed
2507-------------------------------------------------------------------------- */
2508void Note()
2509{
2510    u_char 	text[256];
2511	u_char	localText[256];
2512    u_int32_t	msgDestination, msgLevel = 0;	// code -> destination
2513    CFStringRef	ref;
2514
2515    SkipBlanks();
2516    /* first get the string, do not perform variable substitution */
2517    PrepStr(text, 0, 0, 0);
2518    /* then get the localized version of the string and perform variable substitution */
2519    localizeString(text, localText, 256);
2520
2521    varSubstitution(localText, SV.strBuf, sizeof(SV.strBuf));
2522
2523    NextInt(&msgLevel);				// get the destination, if present
2524    switch (msgLevel) {
2525        case 1:
2526            msgDestination = kUserMsgFLog;
2527            break;
2528        case 2:
2529        default:
2530            msgDestination = kUserMsgFStatus;
2531            break;
2532        case 3:
2533            msgDestination = kUserMsgFLog | kUserMsgFStatus;
2534            break;
2535    }
2536
2537    // localText switching from P-string to C-string
2538    bcopy(&SV.strBuf[1], &localText[0], SV.strBuf[0]);
2539    localText[SV.strBuf[0]] = '\0';
2540
2541    if (serviceID && (msgDestination & 2)) {
2542        ref = CFStringCreateWithCString(NULL, (char*) localText, kCFStringEncodingUTF8);
2543        if (ref) {
2544            publish_entry(serviceID, kSCPropNetModemNote, ref);
2545            CFRelease(ref);
2546        }
2547    }
2548
2549    if (msgDestination & 1) {
2550		sLog("%s", localText);
2551    }
2552}
2553
2554
2555/* --------------------------------------------------------------------------
2556Returns "true" if entire buffer is written with character delays. Returns
2557"false" if write processing continues in WriteContinue because character
2558delays are being inserted
2559-------------------------------------------------------------------------- */
2560u_int8_t Write()
2561{
2562    u_int32_t	isVarString;
2563    u_int16_t	i, j;
2564    u_int32_t	varIndex;
2565    char 	text[256];
2566
2567    SkipBlanks();
2568    PrepStr(SV.strBuf, &isVarString, &varIndex, 1);
2569
2570    if (isVarString && (varIndex == vsPassWord || ((varIndex == vsAsk) && LastAskedMasked))) {
2571        VerboseBuffer[0] = SV.strBuf[0];
2572        for (i = 1; i <= SV.strBuf[0]; i++)
2573            VerboseBuffer[i] = '�';
2574    }
2575    else {
2576       for (i = 1,  j = 1; i <= SV.strBuf[0] && j < 256; i++) {
2577            u_int8_t c = SV.strBuf[i];
2578            if (c < 0x20) {
2579                VerboseBuffer[j++] = '\\';
2580                VerboseBuffer[j++] = '0' + c / 10;
2581                VerboseBuffer[j++] = '0' + c % 10;
2582            }
2583            else {
2584                VerboseBuffer[j++] = c;
2585            }
2586        }
2587        VerboseBuffer[0] = j - 1;
2588    }
2589
2590	bcopy(&VerboseBuffer[1], &text[0], VerboseBuffer[0]);
2591	text[VerboseBuffer[0]] = 0;
2592    if (verbose) {
2593		sLog("CCLWrite : %s", text);
2594    }
2595
2596    //
2597    // skip the pascal string length byte
2598    //
2599    SV.writeBufIndex = 1;
2600
2601    if (SV.chrDelayValue) {
2602        // complete asynchronously through WriteContinue
2603        WriteContinue();
2604        ScheduleTimer(kCharDelayTimer, SV.chrDelayValue);
2605        return 0;
2606    }
2607    else {
2608
2609        write(outfd, &SV.strBuf[1], SV.strBuf[0]);
2610		if(signalerror)
2611			terminate(signalerror);
2612        return 1;
2613    }
2614}
2615
2616/* --------------------------------------------------------------------------
2617Continue the current write command.  This routine is called when
2618the char delay timer expires
2619-------------------------------------------------------------------------- */
2620void WriteContinue()
2621{
2622
2623    //syslog(LOG_INFO, " ----> delayed '%c'\n", SV.strBuf[SV.writeBufIndex]);
2624
2625    write(outfd, &SV.strBuf[SV.writeBufIndex], 1);
2626	if(signalerror)
2627		terminate(signalerror);
2628    if (SV.writeBufIndex < SV.strBuf[0] )		// if this char is not the last char
2629        SV.writeBufIndex++;			// 		bump index to the next char
2630
2631    return;
2632}
2633
2634/* --------------------------------------------------------------------------
2635the timer scheduled by fScript -> ScriptStartTimer() has expired.
2636only one timer can be active at a time.  the timer type specified in
2637ScriptStartTimer is fed into TimerExpired as the long parameter
2638-------------------------------------------------------------------------- */
2639void TimerExpired(long type)
2640{
2641
2642    if (SV.ctlFlags & cclPlaying) {
2643        switch (type)  {
2644
2645            case kMatchReadTimer:
2646                StopRead();
2647                RunScript();					// go get the next ccl cmd
2648                break;
2649
2650            case kCharDelayTimer:
2651                if (SV.writeBufIndex == SV.strBuf[0] ) {		// this is the last char to write
2652                    WriteContinue();			// write the char
2653                    RunScript();				// go get the next ccl cmd
2654                }
2655                else {
2656                    if (SV.writeBufIndex < SV.strBuf[0]) {	// this char is not the last char
2657                        WriteContinue();	// write the char
2658                        ScheduleTimer(kCharDelayTimer, SV.chrDelayValue);
2659                    }
2660                }
2661
2662                break;
2663        }
2664    }
2665}
2666
2667/* --------------------------------------------------------------------------
2668-------------------------------------------------------------------------- */
2669u_int8_t Ask()
2670{
2671    u_int32_t			maskflag, flags = 0, label;
2672    CFStringRef			ref, resp;
2673    CFUserNotificationRef 	alert;
2674    CFOptionFlags 		alertflags;
2675    CFMutableDictionaryRef 	dict;
2676    SInt32 			error;
2677    CFMutableArrayRef 		array;
2678    u_char 		        text[256];
2679    u_char 		        localText[256];
2680
2681    if (alertname[0] == 0)
2682        return 0;	// no alert to display
2683
2684#define kCmdAskAllowCancel 	1
2685#define kCmdAskAllowEntry 	2
2686#define kCmdAskMaskEntry 	4
2687
2688    if (NextInt(&maskflag))
2689        maskflag = 0;
2690
2691    SkipBlanks();
2692    /* first get the string, do not perform variable substitution */
2693    PrepStr(text, 0, 0, 0);
2694
2695    /* then get the localized version of the string and perform variable substitution */
2696    localizeString(text, localText, 256);
2697
2698    varSubstitution(localText, SV.strBuf, sizeof(SV.strBuf));
2699
2700    if (NextInt(&label)) {
2701        SV.askLabel = 0;
2702        flags &= ~kCmdAskAllowCancel;
2703    }
2704    else {
2705        SV.askLabel = (u_int16_t) label;
2706        flags |=  kCmdAskAllowCancel;
2707    }
2708
2709    // default: allow user data entry
2710    flags |= kCmdAskAllowEntry;
2711    switch (maskflag) {
2712        // echo user's input
2713        case 0:
2714            flags &= ~kCmdAskMaskEntry;
2715            break;
2716        // mask user's input with bullets
2717        case 1:
2718            flags |=  kCmdAskMaskEntry;
2719            // Set the fLastAskMasked here (gcg)
2720            LastAskedMasked = maskflag;
2721            break;
2722        // do not allow data entry
2723        case 2:
2724            flags &= ~kCmdAskAllowEntry;
2725            break;
2726    }
2727
2728    ref = CFStringCreateWithPascalString(NULL, SV.strBuf, kCFStringEncodingUTF8);
2729    if (ref) {
2730        dict = CFDictionaryCreateMutable(NULL, 0,
2731                &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2732        if (dict) {
2733
2734            alertflags = 0;
2735
2736            if (alertNameRef)
2737                CFDictionaryAddValue(dict, kCFUserNotificationAlertHeaderKey, alertNameRef);
2738
2739            // it is either a text field, or a message
2740            if (flags & kCmdAskAllowEntry) {
2741                array = CFArrayCreateMutable(NULL, 0, NULL);
2742                if (array) {
2743                    CFArrayAppendValue(array, ref);
2744                    CFDictionaryAddValue(dict, kCFUserNotificationTextFieldTitlesKey, array);
2745                    CFRelease(array);
2746                    if (flags & kCmdAskMaskEntry)
2747                        alertflags = CFUserNotificationSecureTextField(0);
2748                }
2749            }
2750            else
2751                CFDictionaryAddValue(dict, kCFUserNotificationAlertMessageKey, ref);
2752
2753            if (cancelNameRef && (flags & kCmdAskAllowCancel))
2754                CFDictionaryAddValue(dict, kCFUserNotificationAlternateButtonTitleKey, cancelNameRef);
2755
2756            if (iconURL)
2757                CFDictionaryAddValue(dict, kCFUserNotificationIconURLKey, iconURL);
2758
2759            if (localBundleURL)
2760                CFDictionaryAddValue(dict, kCFUserNotificationLocalizationURLKey, localBundleURL);
2761
2762            alert = CFUserNotificationCreate(NULL, 0, alertflags, &error, dict);
2763            if (alert) {
2764                CFUserNotificationReceiveResponse(alert, 0, &alertflags);
2765                // the 2 lower bits of the response flags will give the button pressed
2766                // 0 --> default
2767                // 1 --> alternate
2768                // 3 --> exit on time out
2769                if ((alertflags & 3) == 1) {
2770                    // user cancelled
2771                    if (SV.askLabel) {
2772                        SV.scriptLine = SV.labels[SV.askLabel - 1];
2773                        SV.askLabel = 0;
2774                    }
2775                }
2776                else {
2777                    // user clicked OK
2778                    if (flags & kCmdAskAllowEntry) {
2779                        resp = CFUserNotificationGetResponseValue(alert, kCFUserNotificationTextFieldValuesKey, 0);
2780                        if (resp) {
2781                            CFStringGetPascalString(resp, text, sizeof(text), kCFStringEncodingUTF8);
2782                            SetVarString(vsAsk, text);
2783                        }
2784                    }
2785                }
2786
2787                CFRelease(alert);
2788            }
2789            CFRelease(dict);
2790        }
2791        CFRelease(ref);
2792    }
2793
2794    return 0;
2795}
2796
2797/* --------------------------------------------------------------------------
2798report modem speed & negotiated compression to client
2799-------------------------------------------------------------------------- */
2800void CommunicatingAt()
2801{
2802    u_int32_t		speed;
2803    CFNumberRef		num;
2804
2805    NextInt(&speed);		// modem speed reported by script
2806    sLog("Communicating at %d bps.", speed);
2807
2808    if (serviceID) {
2809        num = CFNumberCreate(NULL, kCFNumberIntType, &speed);
2810        if (num) {
2811            publish_entry(serviceID, kSCPropNetModemConnectSpeed, num);
2812            CFRelease(num);
2813        }
2814    }
2815}
2816
2817/* --------------------------------------------------------------------------
2818report various events to client
2819// UserHook 1 == claim the Serial Port for an incoming/outgoing call.
2820// UserHook 2 == report that the modem is doing error correction (other than MNP 10).
2821// UserHook 3 == request that ARA turn off data compression.
2822// UserHook 4 == report that the modem is doing MNP 10 error correction.
2823-------------------------------------------------------------------------- */
2824void UserHook()
2825{
2826    u_int32_t	event;			// event reported by script
2827
2828    NextInt( &event );			// get the event
2829
2830#if 0
2831    // now use a PrivateMessage to send 'event' up the Stream, to whom it may concern.
2832
2833    UInt8	len = 0;
2834    TStreamMessage	*mp;
2835
2836    len = sizeof( ARA_notify_msg ) + sizeof( UInt32 );
2837    mp = new ( len ) TStreamMessage;
2838    if( !mp )
2839    {
2840        DebugStop("\p TCCLScript::UserHook: no memory for message!");
2841        return;		// as if everything is OK, so we expect to fail gracefully later.
2842    }
2843    mp->Reset( len );
2844    mp->SetType( M_PCPROTO );
2845
2846    ARA_notify_msg* PrvMsg = (ARA_notify_msg*) mp->GetDataPointer();
2847
2848    PrvMsg->PRIM_type	= kARAPrvMsg;		// always.
2849    PrvMsg->CODE_type	= kNotifyMsg;		// always.
2850    PrvMsg->MODL_dst	= kAnyModuleId;		// 'any '.
2851    PrvMsg->MODL_src	= kScriptModuleId;	// 'scri'.
2852    PrvMsg->MSG_type	= kNotifyUserHook;
2853    PrvMsg->MSG_error	= 0;
2854    PrvMsg->MSG_flags	= 0;
2855
2856    *((UInt32*) (PrvMsg + 1)) = event;		// put the event type just behind the header
2857
2858    fScriptMod -> ScriptSendDataUp( mp );	// send this up to the Controller.
2859#endif
2860}
2861
2862/* --------------------------------------------------------------------------
2863-------------------------------------------------------------------------- */
2864void ScheduleTimer(long type, u_int32_t milliseconds)
2865{
2866    if (milliseconds)
2867        timeout((void*)TimerExpired, (void*)type, milliseconds);
2868    else
2869        untimeout((void*)TimerExpired, (void*)type);
2870}
2871
2872/* --------------------------------------------------------------------------
2873Compare the specified VarStr with the parsed string.
2874If the strings match, return the parsed label, else return 0
2875-------------------------------------------------------------------------- */
2876int IfStr()
2877{
2878    u_int8_t		noMatch = 0, *src;
2879    u_int32_t		i = 0, strIndex, labelIndex;
2880
2881    NextInt( &strIndex );				// this is which varString to check
2882
2883    src = GetVarString(strIndex);
2884
2885    /* pull out the search string - if nil return state = continue	*/
2886
2887    NextInt(&i);					// parse the jump label, if strings match
2888    labelIndex = SV.labels[i - 1];			// save the jump label (??)
2889    SkipBlanks();					// skip to the match string
2890    PrepStr(SV.strBuf, 0, 0, 1);				// fetch the match string
2891
2892    if (equalstring(&SV.strBuf[0], src)) {	// returns TRUE if they match...
2893        return labelIndex;
2894    }
2895    else {
2896        return noMatch;
2897    }
2898}
2899
2900/* --------------------------------------------------------------------------
2901-------------------------------------------------------------------------- */
2902void ReceiveMatchData(u_int8_t nextChar)
2903{
2904    int32_t		matchIndex;
2905
2906    if (debuglevel & 0x1) {
2907        if (usestderr) {
2908            fprintf(stderr, "%c", nextChar);
2909        }
2910    }
2911
2912    if ((matchIndex = MatchFind(nextChar)) != 0) {
2913        // On a match we clear the pending flag and cancel the match timer
2914        SV.scriptLine = SV.matchStr[--matchIndex].matchLine;
2915        SV.ctlFlags &= ~cclMatchPending;
2916        ScheduleTimer(kMatchReadTimer, 0);
2917	StopRead();
2918        RunScript();
2919    }
2920}
2921
2922/* --------------------------------------------------------------------------
2923flush the read queue.  free all messages going upstream.
2924-------------------------------------------------------------------------- */
2925void Flush()
2926{
2927    tcflush(infd, TCIFLUSH);
2928}
2929
2930/* --------------------------------------------------------------------------
2931Issue an asychronous call to the serial driver to set the handshaking options
2932-------------------------------------------------------------------------- */
2933void HSReset()
2934{
2935    u_int32_t	outputXON_OFF, outputCTS, XonChar, XoffChar, inputXON_OFF, inputDTR;
2936    struct termios 	tios;
2937
2938    if (tcgetattr(outfd, &tios) < 0)
2939        return;
2940
2941    NextInt(&outputXON_OFF);
2942    // if this bit is set, use the XON/XOFF chars for in-band, output flow control.
2943    // users shouldn't be doing this.  output of compressor could be the XOFF
2944    // flow control character, and thereby throttle the output stream.
2945    if (outputXON_OFF)
2946        tios.c_iflag |= IXON;
2947    else
2948        tios.c_iflag &= ~IXON;
2949
2950    NextInt(&outputCTS);
2951    // if this bit is set, use CTS for out-band, output flow control.
2952    // ARA calls this signal outputCTS, while Open Transport calls it kOTSrlCTSInputHandshake.
2953    // we both mean that if the modem deasserts CTS, the Mac
2954    // shouldn't send it more data, until the modem reasserts CTS.
2955    // this scheme depends on a correctly wired modem cable.
2956    if (outputCTS)
2957        tios.c_cflag |= CCTS_OFLOW;
2958    else
2959        tios.c_cflag &= ~CCTS_OFLOW;
2960
2961    // default onChar is 0x11, offChar is 0x13.
2962    // users should NEVER be specifying an alternate XonChar or XoffChar.
2963    // when the script is pre-flighted, any attempt to specify a char,
2964    // whether it's an alternate character or a default character, will barf.
2965    // therefore no error checking is done here.  assume these parms will be zero.  skip 'em.
2966    NextInt(&XonChar);
2967    NextInt(&XoffChar);
2968    tios.c_cc[VSTOP] = XoffChar;	/* DC3 = XOFF = ^S */
2969    tios.c_cc[VSTART] = XonChar;	/* DC1 = XON  = ^Q */
2970
2971    NextInt(&inputXON_OFF);
2972    // if this bit is set, use the XON/XOFF chars for in-band, input flow control.
2973    // users shouldn't be doing this.  output of compressor could be the XOFF
2974    // flow control character, and thereby throttle the input stream.
2975    if (inputXON_OFF)
2976        tios.c_iflag |= IXOFF;
2977    else
2978        tios.c_iflag &= ~IXOFF;
2979
2980    NextInt(&inputDTR);
2981    // if this bit is set, use DTR for out-band, input flow control.
2982    // ARA calls this signal inputDTR, while Open Transport calls it kOTSrlDTROutputHandshake.
2983    // we both mean that if the Mac deasserts DTR,
2984    // the modem shouldn't send it more data, until the Mac reasserts DTR.
2985    // this scheme depends on a correctly wired modem cable.
2986    if (inputDTR)
2987        tios.c_cflag |= CRTS_IFLOW;
2988    else
2989        tios.c_cflag &= ~CRTS_IFLOW;
2990
2991    // set the same settings for inout and output fd
2992    tcsetattr(infd, TCSAFLUSH, &tios);
2993    tcsetattr(outfd, TCSAFLUSH, &tios);
2994}
2995
2996/* --------------------------------------------------------------------------
2997Issue an asychronous call to the serial driver to set
2998DTR or Clear DTR, depending on the opcode passed in
2999-------------------------------------------------------------------------- */
3000void DTRCommand(short DTRCode)
3001{
3002    ioctl(infd, DTRCode == DTR_SET ? TIOCSDTR : TIOCCDTR);
3003}
3004
3005/* --------------------------------------------------------------------------
3006Send up a user notification about the current CCL exit error.
3007-------------------------------------------------------------------------- */
3008static char* cclErrStrings[] =
3009{
3010    "internal error used to abort match read",	// cclErr_AbortMatchRead = -6000
3011    "Bad parameter given to the engine",		// cclErr_BadParameter = -6001
3012    "Duplicate label",							// cclErr_DuplicateLabel = -6002
3013    "Label undefined",							// cclErr_LabelUndefined = -6003
3014    "Subroutine overflow",					// cclErr_SubroutineOverFlow = -6004
3015    "No memory...",								// cclErr_NoMemErr = -6005
3016    "CCL error base",						// (in the middle?) cclErr = -6006
3017    "There is at least one script open",		// cclErr_CloseError = -6007
3018    "Script Canceled",						// cclErr_ScriptCancelled = -6008
3019    "Script contains too many lines",			// cclErr_TooManyLines = -6009
3020    "Script contains too many characters",		// cclErr_ScriptTooBig = -6010
3021    "CCL has not been initialized",				// cclErr_NotInitialized = -6011
3022    "Cancel in progress.",					// cclErr_CancelInProgress = -6012
3023    "Play command already in progress.",		// cclErr_PlayInProgress = -6013
3024    "Exit with no error.",						// cclErr_ExitOK = -6014
3025    "Label out of range.",						// cclErr_BadLabel = -6015
3026    "Bad command.",								// cclErr_BadCommand = -6016
3027    "End of script reached, expecting Exit.",	// cclErr_EndOfScriptErr = -6017
3028    "Match string index is out of bounds.",	// cclErr_MatchStrIndxErr = -6018
3029    "Modem error, modem not responding.",		// cclErr_ModemErr = -6019
3030    "No dial tone.",							// cclErr_NoDialTone = -6020
3031    "No carrier.",								// cclErr_NoCarrierErr = -6021
3032    "Line busy.",								// cclErr_LineBusyErr = -6022
3033    "No answer.",								// cclErr_NoAnswerErr = -6023
3034    "No @ORIGINATE label",					// cclErr_NoOriginateLabel = -6024
3035    "No @ANSWER label",							// cclErr_NoAnswerLabel = -6025
3036    "No @HANGUP label",							// cclErr_NoHangUpLabel = -6026
3037    "Can't connect because number is empty.",	// cclErr_NoNumberErr = -6027
3038    "Incorrect script for the modem."			// cclErr_BadScriptErr = -6028
3039};
3040
3041void terminate(int exitError)
3042{
3043    //u_long m = exitError;
3044#if 0
3045    CFNumberRef		num;
3046#endif
3047#if 1
3048    if (verbose || exitError != 0) {
3049		char *errStr = NULL;
3050
3051		if (exitError == cclErr_DuplicateLabel) {
3052			// null terminate the P-string :P
3053			if (SV.strBuf[0]) {
3054				SV.strBuf[SV.strBuf[0]+1] = '\0';
3055				errStr = (char*)(&SV.strBuf[0] + 1);
3056			} else {
3057				// empty custom error
3058				errStr = "<no custom error>";
3059			}
3060		} else if (exitError <= cclErr_AbortMatchRead &&
3061				exitError >= cclErr_BadScriptErr)
3062			errStr = cclErrStrings[cclErr_AbortMatchRead - exitError];
3063
3064		if (errStr)
3065			sLog("CCLExit: %d (%s)", exitError, errStr);
3066		else
3067			sLog("CCLExit: %d", exitError);
3068    }
3069#endif
3070    //if (ppplink != -1)
3071    //    sys_send_confd(csockfd, PPP_CCLRESULT, (u_char *)&m, sizeof(m), ppplink, 0);
3072
3073     // connect and listen mode always publish last cause
3074     // disconnect mode publish only non-null cause
3075     // last cause displays connection error, even when plays disconnect sequence
3076#if 0
3077    if (serviceID && (exitError || (enginemode != mode_disconnect))) {
3078        num = CFNumberCreate(NULL, kCFNumberIntType, &exitError);
3079        if (num) {
3080            publish_entry(serviceID, kSCPropNetPPPLastCause, num);
3081            CFRelease(num);
3082        }
3083    }
3084#endif
3085
3086    /* unpublish unnecessary information */
3087    if (serviceID) {
3088        unpublish_entry(serviceID, kSCPropNetModemNote);
3089    }
3090
3091    close(infd);
3092    close(outfd);
3093    exit(exitError);
3094}
3095
3096/* --------------------------------------------------------------------------
3097* timeout - Schedule a timeout.
3098* Note that this timeout takes the number of milliseconds, NOT hz (as in
3099* the kernel).
3100-------------------------------------------------------------------------- */
3101void timeout(void (*func)(void *), void *arg, u_long time)
3102{
3103    struct callout *newp, *p, **pp;
3104
3105    /* Allocate timeout. */
3106    if ((newp = (struct callout *) malloc(sizeof(struct callout))) == NULL)
3107        terminate(cclErr_NoMemErr);
3108
3109    newp->c_arg = arg;
3110    newp->c_func = func;
3111    gettimeofday(&timenow, NULL);
3112    newp->c_time.tv_sec = timenow.tv_sec + time/1000;
3113    newp->c_time.tv_usec = timenow.tv_usec + ((time%1000)*1000);
3114    if (newp->c_time.tv_usec >= 1000000) {
3115        newp->c_time.tv_sec++;
3116        newp->c_time.tv_usec -= 1000000;
3117    }
3118
3119    /*
3120     * Find correct place and link it in.
3121     */
3122    for (pp = &callout; (p = *pp); pp = &p->c_next)
3123        if (newp->c_time.tv_sec < p->c_time.tv_sec
3124            || (newp->c_time.tv_sec == p->c_time.tv_sec
3125                && newp->c_time.tv_usec < p->c_time.tv_usec))
3126            break;
3127    newp->c_next = p;
3128    *pp = newp;
3129}
3130
3131
3132/* --------------------------------------------------------------------------
3133* untimeout - Unschedule a timeout.
3134-------------------------------------------------------------------------- */
3135void untimeout(void (*func)(void *), void *arg)
3136{
3137    struct callout **copp, *freep;
3138
3139    /*
3140     * Find first matching timeout and remove it from the list.
3141     */
3142    for (copp = &callout; (freep = *copp); copp = &freep->c_next)
3143        if (freep->c_func == func && freep->c_arg == arg) {
3144            *copp = freep->c_next;
3145            //printf("untimeout [ppp%d]\n", ((struct fsm *)arg)->unit);
3146            free((char *) freep);
3147            break;
3148        }
3149}
3150
3151/* --------------------------------------------------------------------------
3152* calltimeout - Call any timeout routines which are now due.
3153-------------------------------------------------------------------------- */
3154void calltimeout()
3155{
3156    struct callout *p;
3157
3158    while (callout != NULL) {
3159        p = callout;
3160
3161        if (gettimeofday(&timenow, NULL) < 0)
3162            terminate(cclErr_NoMemErr);
3163
3164        if (!(p->c_time.tv_sec < timenow.tv_sec
3165              || (p->c_time.tv_sec == timenow.tv_sec
3166                  && p->c_time.tv_usec <= timenow.tv_usec)))
3167            break;		/* no, it's not time yet */
3168
3169        callout = p->c_next;
3170        (*p->c_func)(p->c_arg);
3171
3172        free((char *) p);
3173    }
3174}
3175
3176/* --------------------------------------------------------------------------
3177* timeleft - return the length of time until the next timeout is due.
3178-------------------------------------------------------------------------- */
3179struct timeval *timeleft(struct timeval *tvp)
3180{
3181    if (callout == NULL)
3182        return NULL;
3183
3184    gettimeofday(&timenow, NULL);
3185    tvp->tv_sec = callout->c_time.tv_sec - timenow.tv_sec;
3186    tvp->tv_usec = callout->c_time.tv_usec - timenow.tv_usec;
3187    if (tvp->tv_usec < 0) {
3188        tvp->tv_usec += 1000000;
3189        tvp->tv_sec -= 1;
3190    }
3191    if (tvp->tv_sec < 0)
3192        tvp->tv_sec = tvp->tv_usec = 0;
3193
3194    return tvp;
3195}
3196
3197/* -----------------------------------------------------------------------------
3198publish a dictionnary entry in the cache
3199----------------------------------------------------------------------------- */
3200int publish_entry(u_char *serviceid, CFStringRef entry, CFTypeRef value)
3201{
3202    CFMutableDictionaryRef	dict;
3203    CFStringRef			key;
3204    SCDynamicStoreRef		cfgCache;
3205    CFPropertyListRef		ref;
3206    int				ret = 0;
3207
3208    if (cfgCache = SCDynamicStoreCreate(0, CFSTR("CCLEngine"), 0, 0)) {
3209        if (key = SCDynamicStoreKeyCreate(0, CFSTR("%@/%@/%@/%s/%@"),
3210                    kSCDynamicStoreDomainState, kSCCompNetwork, kSCCompService, serviceid, kSCEntNetModem)) {
3211
3212            if (ref = SCDynamicStoreCopyValue(cfgCache, key)) {
3213                dict = CFDictionaryCreateMutableCopy(0, 0, ref);
3214                CFRelease(ref);
3215            }
3216            else
3217                dict = CFDictionaryCreateMutable(0, 0,
3218                            &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
3219
3220            if (dict) {
3221                CFDictionarySetValue(dict, entry, value);
3222                ret = SCDynamicStoreSetValue(cfgCache, key, dict);
3223                CFRelease(dict);
3224            }
3225
3226            CFRelease(key);
3227        }
3228        CFRelease(cfgCache);
3229    }
3230    return ret;
3231}
3232
3233/* -----------------------------------------------------------------------------
3234unpublish a disctionnary entry from the cache
3235----------------------------------------------------------------------------- */
3236int unpublish_entry(u_char *serviceid, CFStringRef entry)
3237{
3238    CFPropertyListRef		ref;
3239    CFMutableDictionaryRef	dict;
3240    CFStringRef			key;
3241    SCDynamicStoreRef		cfgCache;
3242    int 			ret = 0;
3243
3244    if (cfgCache = SCDynamicStoreCreate(0, CFSTR("CCLEngine"), 0, 0)) {
3245
3246        if (key = SCDynamicStoreKeyCreate(0, CFSTR("%@/%@/%@/%s/%@"),
3247                    kSCDynamicStoreDomainState, kSCCompNetwork, kSCCompService, serviceid, kSCEntNetModem)) {
3248
3249            if (ref = SCDynamicStoreCopyValue(cfgCache, key)) {
3250                if (dict = CFDictionaryCreateMutableCopy(0, 0, ref)) {
3251                    CFDictionaryRemoveValue(dict, entry);
3252                    ret = SCDynamicStoreSetValue(cfgCache, key, dict);
3253                    CFRelease(dict);
3254                }
3255                CFRelease(ref);
3256            }
3257            CFRelease(key);
3258        }
3259        CFRelease(cfgCache);
3260    }
3261    return ret;
3262}
3263
3264
3265