Clean up code in app_adsiprog.
[asterisk/asterisk.git] / apps / app_adsiprog.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2005, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  *
21  * \brief Program Asterisk ADSI Scripts into phone
22  *
23  * \author Mark Spencer <markster@digium.com>
24  * 
25  * \ingroup applications
26  */
27
28 /*** MODULEINFO
29         <depend>res_adsi</depend>
30  ***/
31
32 #include "asterisk.h"
33
34 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
35
36 #include <sys/types.h>
37 #include <netinet/in.h>
38 #include <stdlib.h>
39 #include <unistd.h>
40 #include <string.h>
41 #include <stdlib.h>
42 #include <ctype.h>
43 #include <stdio.h>
44 #include <errno.h>
45
46 #include "asterisk/file.h"
47 #include "asterisk/logger.h"
48 #include "asterisk/channel.h"
49 #include "asterisk/pbx.h"
50 #include "asterisk/module.h"
51 #include "asterisk/adsi.h"
52 #include "asterisk/options.h"
53 #include "asterisk/utils.h"
54 #include "asterisk/lock.h"
55
56 static char *app = "ADSIProg";
57
58 static char *synopsis = "Load Asterisk ADSI Scripts into phone";
59
60 /* #define DUMP_MESSAGES */
61
62 static char *descrip =
63 "  ADSIProg(script): This application programs an ADSI Phone with the given\n"
64 "script. If nothing is specified, the default script (asterisk.adsi) is used.\n";
65
66 struct adsi_event {
67         int id;
68         char *name;
69 };
70
71 static struct adsi_event events[] = {
72         { 1, "CALLERID" },
73         { 2, "VMWI" },
74         { 3, "NEARANSWER" },
75         { 4, "FARANSWER" },
76         { 5, "ENDOFRING" },
77         { 6, "IDLE" },
78         { 7, "OFFHOOK" },
79         { 8, "CIDCW" },
80         { 9, "BUSY" },
81         { 10, "FARRING" },
82         { 11, "DIALTONE" },
83         { 12, "RECALL" },
84         { 13, "MESSAGE" },
85         { 14, "REORDER" },
86         { 15, "DISTINCTIVERING" },
87         { 16, "RING" },
88         { 17, "REMINDERRING" },
89         { 18, "SPECIALRING" },
90         { 19, "CODEDRING" },
91         { 20, "TIMER" },
92         { 21, "INUSE" },
93         { 22, "EVENT22" },
94         { 23, "EVENT23" },
95         { 24, "CPEID" },
96 };
97
98 static struct adsi_event justify[] = {
99         { 0, "CENTER" },
100         { 1, "RIGHT" },
101         { 2, "LEFT" },
102         { 3, "INDENT" },
103 };
104
105 #define STATE_NORMAL            0
106 #define STATE_INKEY             1
107 #define STATE_INSUB             2
108 #define STATE_INIF              3
109
110 #define MAX_RET_CODE            20
111 #define MAX_SUB_LEN             255
112 #define MAX_MAIN_LEN            1600
113
114 #define ARG_STRING              (1 << 0)
115 #define ARG_NUMBER              (1 << 1)
116
117 struct adsi_soft_key {
118         char vname[40];         /* Which "variable" is associated with it */
119         int retstrlen;          /* Length of return string */
120         int initlen;            /* initial length */
121         int id;
122         int defined;
123         char retstr[80];        /* Return string data */
124 };
125
126 struct adsi_subscript {
127         char vname[40];
128         int id;
129         int defined;
130         int datalen;
131         int inscount;
132         int ifinscount;
133         char *ifdata;
134         char data[2048];
135 };
136
137 struct adsi_state {
138         char vname[40];
139         int id;
140 };
141
142 struct adsi_flag {
143         char vname[40];
144         int id;
145 };
146
147 struct adsi_display {
148         char vname[40];
149         int id;
150         char data[70];
151         int datalen;
152 };
153
154 struct adsi_script {
155         int state;
156         int numkeys;
157         int numsubs;
158         int numstates;
159         int numdisplays;
160         int numflags;
161         struct adsi_soft_key *key;
162         struct adsi_subscript *sub;
163         /* Pre-defined displays */
164         struct adsi_display displays[63];
165         /* ADSI States 1 (initial) - 254 */
166         struct adsi_state states[256];
167         /* Keys 2-63 */
168         struct adsi_soft_key keys[62];
169         /* Subscripts 0 (main) to 127 */
170         struct adsi_subscript subs[128];
171         /* Flags 1-7 */
172         struct adsi_flag flags[7];
173
174         /* Stuff from adsi script */
175         unsigned char sec[5];
176         char desc[19];
177         unsigned char fdn[5];
178         int ver;
179 };
180
181
182 static int process_token(void *out, char *src, int maxlen, int argtype)
183 {
184         if ((strlen(src) > 1) && src[0] == '\"') {
185                 /* This is a quoted string */
186                 if (!(argtype & ARG_STRING))
187                         return -1;
188                 src++;
189                 /* Don't take more than what's there */
190                 if (maxlen > strlen(src) - 1)
191                         maxlen = strlen(src) - 1;
192                 memcpy(out, src, maxlen);
193                 ((char *)out)[maxlen] = '\0';
194         } else if (!ast_strlen_zero(src) && (src[0] == '\\')) {
195                 if (!(argtype & ARG_NUMBER))
196                         return -1;
197                 /* Octal value */
198                 if (sscanf(src, "%o", (int *)out) != 1)
199                         return -1;
200                 if (argtype & ARG_STRING) {
201                         /* Convert */
202                         *((unsigned int *)out) = htonl(*((unsigned int *)out));
203                 }
204         } else if ((strlen(src) > 2) && (src[0] == '0') && (tolower(src[1]) == 'x')) {
205                 if (!(argtype & ARG_NUMBER))
206                         return -1;
207                 /* Hex value */
208                 if (sscanf(src + 2, "%x", (unsigned int *)out) != 1)
209                         return -1;
210                 if (argtype & ARG_STRING) {
211                         /* Convert */
212                         *((unsigned int *)out) = htonl(*((unsigned int *)out));
213                 }
214         } else if ((!ast_strlen_zero(src) && isdigit(src[0]))) {
215                 if (!(argtype & ARG_NUMBER))
216                         return -1;
217                 /* Hex value */
218                 if (sscanf(src, "%d", (int *)out) != 1)
219                         return -1;
220                 if (argtype & ARG_STRING) {
221                         /* Convert */
222                         *((unsigned int *)out) = htonl(*((unsigned int *)out));
223                 }
224         } else
225                 return -1;
226         return 0;
227 }
228
229 static char *get_token(char **buf, char *script, int lineno)
230 {
231         char *tmp = *buf, *keyword;
232         int quoted = 0;
233
234         /* Advance past any white space */
235         while(*tmp && (*tmp < 33))
236                 tmp++;
237         if (!*tmp)
238                 return NULL;
239         keyword = tmp;
240         while(*tmp && ((*tmp > 32)  || quoted)) {
241                 if (*tmp == '\"') {
242                         quoted = !quoted;
243                 }
244                 tmp++;
245         }
246         if (quoted) {
247                 ast_log(LOG_WARNING, "Mismatched quotes at line %d of %s\n", lineno, script);
248                 return NULL;
249         }
250         *tmp = '\0';
251         tmp++;
252         while(*tmp && (*tmp < 33))
253                 tmp++;
254         /* Note where we left off */
255         *buf = tmp;
256         return keyword;
257 }
258
259 static char *validdtmf = "123456789*0#ABCD";
260
261 static int send_dtmf(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno)
262 {
263         char dtmfstr[80], *a;
264         int bytes = 0;
265
266         if (!(a = get_token(&args, script, lineno))) {
267                 ast_log(LOG_WARNING, "Expecting something to send for SENDDTMF at line %d of %s\n", lineno, script);
268                 return 0;
269         }
270
271         if (process_token(dtmfstr, a, sizeof(dtmfstr) - 1, ARG_STRING)) {
272                 ast_log(LOG_WARNING, "Invalid token for SENDDTMF at line %d of %s\n", lineno, script);
273                 return 0;
274         }
275
276         a = dtmfstr;
277
278         while(*a) {
279                 if (strchr(validdtmf, *a)) {
280                         *buf = *a;
281                         buf++;
282                         bytes++;
283                 } else
284                         ast_log(LOG_WARNING, "'%c' is not a valid DTMF tone at line %d of %s\n", *a, lineno, script);
285                 a++;
286         }
287
288         return bytes;
289 }
290
291 static int goto_line(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno)
292 {
293         char *page = get_token(&args, script, lineno);
294         char *gline = get_token(&args, script, lineno);
295         int line;
296         unsigned char cmd;
297
298         if (!page || !gline) {
299                 ast_log(LOG_WARNING, "Expecting page and line number for GOTOLINE at line %d of %s\n", lineno, script);
300                 return 0;
301         }
302
303         if (!strcasecmp(page, "INFO")) {
304                 cmd = 0;
305         } else if (!strcasecmp(page, "COMM")) {
306                 cmd = 0x80;
307         } else {
308                 ast_log(LOG_WARNING, "Expecting either 'INFO' or 'COMM' page, got got '%s' at line %d of %s\n", page, lineno, script);
309                 return 0;
310         }
311
312         if (process_token(&line, gline, sizeof(line), ARG_NUMBER)) {
313                 ast_log(LOG_WARNING, "Invalid line number '%s' at line %d of %s\n", gline, lineno, script);
314                 return 0;
315         }
316
317         cmd |= line;
318         buf[0] = 0x8b;
319         buf[1] = cmd;
320
321         return 2;
322 }
323
324 static int goto_line_rel(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno)
325 {
326         char *dir = get_token(&args, script, lineno);
327         char *gline = get_token(&args, script, lineno);
328         int line;
329         unsigned char cmd;
330
331         if (!dir || !gline) {
332                 ast_log(LOG_WARNING, "Expecting direction and number of lines for GOTOLINEREL at line %d of %s\n", lineno, script);
333                 return 0;
334         }
335
336         if (!strcasecmp(dir, "UP")) {
337                 cmd = 0;
338         } else if (!strcasecmp(dir, "DOWN")) {
339                 cmd = 0x20;
340         } else {
341                 ast_log(LOG_WARNING, "Expecting either 'UP' or 'DOWN' direction, got '%s' at line %d of %s\n", dir, lineno, script);
342                 return 0;
343         }
344
345         if (process_token(&line, gline, sizeof(line), ARG_NUMBER)) {
346                 ast_log(LOG_WARNING, "Invalid line number '%s' at line %d of %s\n", gline, lineno, script);
347                 return 0;
348         }
349
350         cmd |= line;
351         buf[0] = 0x8c;
352         buf[1] = cmd;
353
354         return 2;
355 }
356
357 static int send_delay(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno)
358 {
359         char *gtime = get_token(&args, script, lineno);
360         int ms;
361
362         if (!gtime) {
363                 ast_log(LOG_WARNING, "Expecting number of milliseconds to wait at line %d of %s\n", lineno, script);
364                 return 0;
365         }
366
367         if (process_token(&ms, gtime, sizeof(ms), ARG_NUMBER)) {
368                 ast_log(LOG_WARNING, "Invalid delay milliseconds '%s' at line %d of %s\n", gtime, lineno, script);
369                 return 0;
370         }
371
372         buf[0] = 0x90;
373
374         if (id == 11)
375                 buf[1] = ms / 100;
376         else
377                 buf[1] = ms / 10;
378
379         return 2;
380 }
381
382 static int set_state(char *buf, char *name, int id, char *args, struct adsi_script *istate, char *script, int lineno)
383 {
384         char *gstate = get_token(&args, script, lineno);
385         int state;
386
387         if (!gstate) {
388                 ast_log(LOG_WARNING, "Expecting state number at line %d of %s\n", lineno, script);
389                 return 0;
390         }
391
392         if (process_token(&state, gstate, sizeof(state), ARG_NUMBER)) {
393                 ast_log(LOG_WARNING, "Invalid state number '%s' at line %d of %s\n", gstate, lineno, script);
394                 return 0;
395         }
396
397         buf[0] = id;
398         buf[1] = state;
399
400         return 2;
401 }
402
403 static int cleartimer(char *buf, char *name, int id, char *args, struct adsi_script *istate, char *script, int lineno)
404 {
405         char *tok = get_token(&args, script, lineno);
406
407         if (tok) 
408                 ast_log(LOG_WARNING, "Clearing timer requires no arguments ('%s') at line %d of %s\n", tok, lineno, script);
409
410         buf[0] = id;
411
412         /* For some reason the clear code is different slightly */
413         if (id == 7)
414                 buf[1] = 0x10;
415         else
416                 buf[1] = 0x00;
417
418         return 2;
419 }
420
421 static struct adsi_flag *getflagbyname(struct adsi_script *state, char *name, char *script, int lineno, int create)
422 {
423         int x;
424
425         for (x = 0; x < state->numflags; x++) {
426                 if (!strcasecmp(state->flags[x].vname, name)) 
427                         return &state->flags[x];
428         }
429
430         /* Return now if we're not allowed to create */
431         if (!create)
432                 return NULL;
433
434         if (state->numflags > 6) {
435                 ast_log(LOG_WARNING, "No more flag space at line %d of %s\n", lineno, script);
436                 return NULL;
437         }
438
439         ast_copy_string(state->flags[state->numflags].vname, name, sizeof(state->flags[state->numflags].vname));
440         state->flags[state->numflags].id = state->numflags + 1;
441         state->numflags++;
442
443         return &state->flags[state->numflags-1];
444 }
445
446 static int setflag(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno)
447 {
448         char *tok = get_token(&args, script, lineno);
449         char sname[80];
450         struct adsi_flag *flag;
451
452         if (!tok) {
453                 ast_log(LOG_WARNING, "Setting flag requires a flag number at line %d of %s\n", lineno, script);
454                 return 0;
455         }
456
457         if (process_token(sname, tok, sizeof(sname) - 1, ARG_STRING)) {
458                 ast_log(LOG_WARNING, "Invalid flag '%s' at line %d of %s\n", tok, lineno, script);
459                 return 0;
460         }
461
462         if (!(flag = getflagbyname(state, sname, script, lineno, 0))) {
463                 ast_log(LOG_WARNING, "Flag '%s' is undeclared at line %d of %s\n", sname, lineno, script);
464                 return 0;
465         }
466
467         buf[0] = id;
468         buf[1] = ((flag->id & 0x7) << 4) | 1;
469
470         return 2;
471 }
472
473 static int clearflag(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno)
474 {
475         char *tok = get_token(&args, script, lineno);
476         struct adsi_flag *flag;
477         char sname[80];
478
479         if (!tok) {
480                 ast_log(LOG_WARNING, "Clearing flag requires a flag number at line %d of %s\n", lineno, script);
481                 return 0;
482         }
483
484         if (process_token(sname, tok, sizeof(sname) - 1, ARG_STRING)) {
485                 ast_log(LOG_WARNING, "Invalid flag '%s' at line %d of %s\n", tok, lineno, script);
486                 return 0;
487         }
488
489         if (!(flag = getflagbyname(state, sname, script, lineno, 0))) {
490                 ast_log(LOG_WARNING, "Flag '%s' is undeclared at line %d of %s\n", sname, lineno, script);
491                 return 0;
492         }
493
494         buf[0] = id;
495         buf[1] = ((flag->id & 0x7) << 4);
496
497         return 2;
498 }
499
500 static int starttimer(char *buf, char *name, int id, char *args, struct adsi_script *istate, char *script, int lineno)
501 {
502         char *tok = get_token(&args, script, lineno);
503         int secs;
504
505         if (!tok) {
506                 ast_log(LOG_WARNING, "Missing number of seconds at line %d of %s\n", lineno, script);
507                 return 0;
508         }
509
510         if (process_token(&secs, tok, sizeof(secs), ARG_NUMBER)) {
511                 ast_log(LOG_WARNING, "Invalid number of seconds '%s' at line %d of %s\n", tok, lineno, script);
512                 return 0;
513         }
514
515         buf[0] = id;
516         buf[1] = 0x1;
517         buf[2] = secs;
518
519         return 3;
520 }
521
522 static int geteventbyname(char *name)
523 {
524         int x;
525
526         for (x = 0; x < sizeof(events) / sizeof(events[0]); x++) {
527                 if (!strcasecmp(events[x].name, name))
528                         return events[x].id;
529         }
530
531         return 0;
532 }
533
534 static int getjustifybyname(char *name)
535 {
536         int x;
537
538         for (x = 0; x <sizeof(justify) / sizeof(justify[0]); x++) {
539                 if (!strcasecmp(justify[x].name, name))
540                         return justify[x].id;
541         }
542
543         return -1;
544 }
545
546 static struct adsi_soft_key *getkeybyname(struct adsi_script *state, char *name, char *script, int lineno)
547 {
548         int x;
549
550         for (x = 0; x < state->numkeys; x++) {
551                 if (!strcasecmp(state->keys[x].vname, name)) 
552                         return &state->keys[x];
553         }
554
555         if (state->numkeys > 61) {
556                 ast_log(LOG_WARNING, "No more key space at line %d of %s\n", lineno, script);
557                 return NULL;
558         }
559
560         ast_copy_string(state->keys[state->numkeys].vname, name, sizeof(state->keys[state->numkeys].vname));
561         state->keys[state->numkeys].id = state->numkeys + 2;
562         state->numkeys++;
563
564         return &state->keys[state->numkeys-1];
565 }
566
567 static struct adsi_subscript *getsubbyname(struct adsi_script *state, char *name, char *script, int lineno)
568 {
569         int x;
570
571         for (x = 0; x < state->numsubs; x++) {
572                 if (!strcasecmp(state->subs[x].vname, name)) 
573                         return &state->subs[x];
574         }
575
576         if (state->numsubs > 127) {
577                 ast_log(LOG_WARNING, "No more subscript space at line %d of %s\n", lineno, script);
578                 return NULL;
579         }
580
581         ast_copy_string(state->subs[state->numsubs].vname, name, sizeof(state->subs[state->numsubs].vname));
582         state->subs[state->numsubs].id = state->numsubs;
583         state->numsubs++;
584
585         return &state->subs[state->numsubs-1];
586 }
587
588 static struct adsi_state *getstatebyname(struct adsi_script *state, char *name, char *script, int lineno, int create)
589 {
590         int x;
591
592         for (x = 0; x <state->numstates; x++) {
593                 if (!strcasecmp(state->states[x].vname, name)) 
594                         return &state->states[x];
595         }
596
597         /* Return now if we're not allowed to create */
598         if (!create)
599                 return NULL;
600
601         if (state->numstates > 253) {
602                 ast_log(LOG_WARNING, "No more state space at line %d of %s\n", lineno, script);
603                 return NULL;
604         }
605
606         ast_copy_string(state->states[state->numstates].vname, name, sizeof(state->states[state->numstates].vname));
607         state->states[state->numstates].id = state->numstates + 1;
608         state->numstates++;
609
610         return &state->states[state->numstates-1];
611 }
612
613 static struct adsi_display *getdisplaybyname(struct adsi_script *state, char *name, char *script, int lineno, int create)
614 {
615         int x;
616
617         for (x = 0; x < state->numdisplays; x++) {
618                 if (!strcasecmp(state->displays[x].vname, name)) 
619                         return &state->displays[x];
620         }
621
622         /* Return now if we're not allowed to create */
623         if (!create)
624                 return NULL;
625
626         if (state->numdisplays > 61) {
627                 ast_log(LOG_WARNING, "No more display space at line %d of %s\n", lineno, script);
628                 return NULL;
629         }
630
631         ast_copy_string(state->displays[state->numdisplays].vname, name, sizeof(state->displays[state->numdisplays].vname));
632         state->displays[state->numdisplays].id = state->numdisplays + 1;
633         state->numdisplays++;
634
635         return &state->displays[state->numdisplays-1];
636 }
637
638 static int showkeys(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno)
639 {
640         char *tok, newkey[80];
641         int bytes, x, flagid = 0;
642         unsigned char keyid[6];
643         struct adsi_soft_key *key;
644         struct adsi_flag *flag;
645
646         for (x = 0; x < 7; x++) {
647                 /* Up to 6 key arguments */
648                 if (!(tok = get_token(&args, script, lineno)))
649                         break;
650                 if (!strcasecmp(tok, "UNLESS")) {
651                         /* Check for trailing UNLESS flag */
652                         if (!(tok = get_token(&args, script, lineno))) {
653                                 ast_log(LOG_WARNING, "Missing argument for UNLESS clause at line %d of %s\n", lineno, script);
654                         } else if (process_token(newkey, tok, sizeof(newkey) - 1, ARG_STRING)) {
655                                 ast_log(LOG_WARNING, "Invalid flag name '%s' at line %d of %s\n", tok, lineno, script);
656                         } else if (!(flag = getflagbyname(state, newkey, script, lineno, 0))) {
657                                 ast_log(LOG_WARNING, "Flag '%s' is undeclared at line %d of %s\n", newkey, lineno, script);
658                         } else
659                                 flagid = flag->id;
660                         if ((tok = get_token(&args, script, lineno)))
661                                 ast_log(LOG_WARNING, "Extra arguments after UNLESS clause: '%s' at line %d of %s\n", tok, lineno, script);
662                         break;
663                 }
664                 if (x > 5) {
665                         ast_log(LOG_WARNING, "Only 6 keys can be defined, ignoring '%s' at line %d of %s\n", tok, lineno, script);
666                         break;
667                 }
668                 if (process_token(newkey, tok, sizeof(newkey) - 1, ARG_STRING)) {
669                         ast_log(LOG_WARNING, "Invalid token for key name: %s\n", tok);  
670                         continue;
671                 }
672                                    
673                 if (!(key = getkeybyname(state, newkey, script, lineno)))
674                         break;
675                 keyid[x] = key->id;
676         }
677         buf[0] = id;
678         buf[1] = (flagid & 0x7) << 3 | (x & 0x7);
679         for (bytes = 0; bytes < x; bytes++) {
680                 buf[bytes + 2] = keyid[bytes];
681         }
682         return 2 + x;
683 }
684
685 static int showdisplay(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno)
686 {
687         char *tok, dispname[80];
688         int line = 0, flag = 0, cmd = 3;
689         struct adsi_display *disp;
690
691         /* Get display */
692         if (!(tok = get_token(&args, script, lineno)) || process_token(dispname, tok, sizeof(dispname) - 1, ARG_STRING)) {
693                 ast_log(LOG_WARNING, "Invalid display name: %s at line %d of %s\n", tok ? tok : "<nothing>", lineno, script);
694                 return 0;
695         }
696
697         if (!(disp = getdisplaybyname(state, dispname, script, lineno, 0))) {
698                 ast_log(LOG_WARNING, "Display '%s' is undefined at line %d of %s\n", dispname, lineno, script);
699                 return 0;
700         }
701
702         if (!(tok = get_token(&args, script, lineno)) || strcasecmp(tok, "AT")) {
703                 ast_log(LOG_WARNING, "Missing token 'AT' at line %d of %s\n", lineno, script);
704                 return 0;
705         }
706
707         /* Get line number */
708         if (!(tok = get_token(&args, script, lineno)) || process_token(&line, tok, sizeof(line), ARG_NUMBER)) {
709                 ast_log(LOG_WARNING, "Invalid line: '%s' at line %d of %s\n", tok ? tok : "<nothing>", lineno, script);
710                 return 0;
711         }
712
713         if ((tok = get_token(&args, script, lineno)) && !strcasecmp(tok, "NOUPDATE")) {
714                 cmd = 1;
715                 tok = get_token(&args, script, lineno);
716         }
717
718         if (tok && !strcasecmp(tok, "UNLESS")) {
719                 /* Check for trailing UNLESS flag */
720                 if (!(tok = get_token(&args, script, lineno))) {
721                         ast_log(LOG_WARNING, "Missing argument for UNLESS clause at line %d of %s\n", lineno, script);
722                 } else if (process_token(&flag, tok, sizeof(flag), ARG_NUMBER)) {
723                         ast_log(LOG_WARNING, "Invalid flag number '%s' at line %d of %s\n", tok, lineno, script);
724                 }
725                 if ((tok = get_token(&args, script, lineno)))
726                         ast_log(LOG_WARNING, "Extra arguments after UNLESS clause: '%s' at line %d of %s\n", tok, lineno, script);
727         }
728                                    
729         buf[0] = id;
730         buf[1] = (cmd << 6) | (disp->id & 0x3f); 
731         buf[2] = ((line & 0x1f) << 3) | (flag & 0x7);
732
733         return 3;
734 }
735
736 static int cleardisplay(char *buf, char *name, int id, char *args, struct adsi_script *istate, char *script, int lineno)
737 {
738         char *tok = get_token(&args, script, lineno);
739
740         if (tok) 
741                 ast_log(LOG_WARNING, "Clearing display requires no arguments ('%s') at line %d of %s\n", tok, lineno, script);
742
743         buf[0] = id;
744         buf[1] = 0x00;
745         return 2;
746 }
747
748 static int digitdirect(char *buf, char *name, int id, char *args, struct adsi_script *istate, char *script, int lineno)
749 {
750         char *tok = get_token(&args, script, lineno);
751
752         if (tok) 
753                 ast_log(LOG_WARNING, "Digitdirect requires no arguments ('%s') at line %d of %s\n", tok, lineno, script);
754
755         buf[0] = id;
756         buf[1] = 0x7;
757         return 2;
758 }
759
760 static int clearcbone(char *buf, char *name, int id, char *args, struct adsi_script *istate, char *script, int lineno)
761 {
762         char *tok = get_token(&args, script, lineno);
763
764         if (tok)
765                 ast_log(LOG_WARNING, "CLEARCB1 requires no arguments ('%s') at line %d of %s\n", tok, lineno, script);
766
767         buf[0] = id;
768         buf[1] = 0;
769         return 2;
770 }
771
772 static int digitcollect(char *buf, char *name, int id, char *args, struct adsi_script *istate, char *script, int lineno)
773 {
774         char *tok = get_token(&args, script, lineno);
775
776         if (tok) 
777                 ast_log(LOG_WARNING, "Digitcollect requires no arguments ('%s') at line %d of %s\n", tok, lineno, script);
778
779         buf[0] = id;
780         buf[1] = 0xf;
781         return 2;
782 }
783
784 static int subscript(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno)
785 {
786         char *tok = get_token(&args, script, lineno);
787         char subscript[80];
788         struct adsi_subscript *sub;
789
790         if (!tok) {
791                 ast_log(LOG_WARNING, "Missing subscript to call at line %d of %s\n", lineno, script);
792                 return 0;
793         }
794
795         if (process_token(subscript, tok, sizeof(subscript) - 1, ARG_STRING)) {
796                 ast_log(LOG_WARNING, "Invalid number of seconds '%s' at line %d of %s\n", tok, lineno, script);
797                 return 0;
798         }
799
800         if (!(sub = getsubbyname(state, subscript, script, lineno)))
801                 return 0;
802
803         buf[0] = 0x9d;
804         buf[1] = sub->id;
805
806         return 2;
807 }
808
809 static int onevent(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno)
810 {
811         char *tok = get_token(&args, script, lineno);
812         char subscript[80], sname[80];
813         int sawin = 0, event, snums[8], scnt = 0, x;
814         struct adsi_subscript *sub;
815
816         if (!tok) {
817                 ast_log(LOG_WARNING, "Missing event for 'ONEVENT' at line %d of %s\n", lineno, script);
818                 return 0;
819         }
820
821         if ((event = geteventbyname(tok)) < 1) {
822                 ast_log(LOG_WARNING, "'%s' is not a valid event name, at line %d of %s\n", args, lineno, script);
823                 return 0;
824         }
825
826         tok = get_token(&args, script, lineno);
827         while ((!sawin && !strcasecmp(tok, "IN")) ||
828                (sawin && !strcasecmp(tok, "OR"))) {
829                 sawin = 1;
830                 if (scnt > 7) {
831                         ast_log(LOG_WARNING, "No more than 8 states may be specified for inclusion at line %d of %s\n", lineno, script);
832                         return 0;
833                 }
834                 /* Process 'in' things */
835                 tok = get_token(&args, script, lineno);
836                 if (process_token(sname, tok, sizeof(sname), ARG_STRING)) {
837                         ast_log(LOG_WARNING, "'%s' is not a valid state name at line %d of %s\n", tok, lineno, script);
838                         return 0;
839                 }
840                 if ((snums[scnt] = getstatebyname(state, sname, script, lineno, 0) < 0)) {
841                         ast_log(LOG_WARNING, "State '%s' not declared at line %d of %s\n", sname, lineno, script);
842                         return 0;
843                 }
844                 scnt++;
845                 if (!(tok = get_token(&args, script, lineno)))
846                         break;
847         }
848         if (!tok || strcasecmp(tok, "GOTO")) {
849                 if (!tok)
850                         tok = "<nothing>";
851                 if (sawin) 
852                         ast_log(LOG_WARNING, "Got '%s' while looking for 'GOTO' or 'OR' at line %d of %s\n", tok, lineno, script);
853                 else
854                         ast_log(LOG_WARNING, "Got '%s' while looking for 'GOTO' or 'IN' at line %d of %s\n", tok, lineno, script);
855         }
856         if (!(tok = get_token(&args, script, lineno))) {
857                 ast_log(LOG_WARNING, "Missing subscript to call at line %d of %s\n", lineno, script);
858                 return 0;
859         }
860         if (process_token(subscript, tok, sizeof(subscript) - 1, ARG_STRING)) {
861                 ast_log(LOG_WARNING, "Invalid subscript '%s' at line %d of %s\n", tok, lineno, script);
862                 return 0;
863         }
864         if (!(sub = getsubbyname(state, subscript, script, lineno)))
865                 return 0;
866         buf[0] = 8;
867         buf[1] = event;
868         buf[2] = sub->id | 0x80;
869         for (x = 0; x < scnt; x++)
870                 buf[3 + x] = snums[x];
871         return 3 + scnt;
872 }
873
874 struct adsi_key_cmd {
875         char *name;
876         int id;
877         int (*add_args)(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno);
878 };
879
880 static struct adsi_key_cmd kcmds[] = {
881         { "SENDDTMF", 0, send_dtmf },
882         /* Encoded DTMF would go here */
883         { "ONHOOK", 0x81 },
884         { "OFFHOOK", 0x82 },
885         { "FLASH", 0x83 },
886         { "WAITDIALTONE", 0x84 },
887         /* Send line number */
888         { "BLANK", 0x86 },
889         { "SENDCHARS", 0x87 },
890         { "CLEARCHARS", 0x88 },
891         { "BACKSPACE", 0x89 },
892         /* Tab column */
893         { "GOTOLINE", 0x8b, goto_line },
894         { "GOTOLINEREL", 0x8c, goto_line_rel },
895         { "PAGEUP", 0x8d },
896         { "PAGEDOWN", 0x8e },
897         /* Extended DTMF */
898         { "DELAY", 0x90, send_delay },
899         { "DIALPULSEONE", 0x91 },
900         { "DATAMODE", 0x92 },
901         { "VOICEMODE", 0x93 },
902         /* Display call buffer 'n' */
903         /* Clear call buffer 'n' */
904         { "CLEARCB1", 0x95, clearcbone },
905         { "DIGITCOLLECT", 0x96, digitcollect },
906         { "DIGITDIRECT", 0x96, digitdirect },
907         { "CLEAR", 0x97 },
908         { "SHOWDISPLAY", 0x98, showdisplay },
909         { "CLEARDISPLAY", 0x98, cleardisplay },
910         { "SHOWKEYS", 0x99, showkeys },
911         { "SETSTATE", 0x9a, set_state },
912         { "TIMERSTART", 0x9b, starttimer },
913         { "TIMERCLEAR", 0x9b, cleartimer },
914         { "SETFLAG", 0x9c, setflag },
915         { "CLEARFLAG", 0x9c, clearflag },
916         { "GOTO", 0x9d, subscript },
917         { "EVENT22", 0x9e },
918         { "EVENT23", 0x9f },
919         { "EXIT", 0xa0 },
920 };
921
922 static struct adsi_key_cmd opcmds[] = {
923         
924         /* 1 - Branch on event -- handled specially */
925         { "SHOWKEYS", 2, showkeys },
926         /* Display Control */
927         { "SHOWDISPLAY", 3, showdisplay },
928         { "CLEARDISPLAY", 3, cleardisplay },
929         { "CLEAR", 5 },
930         { "SETSTATE", 6, set_state },
931         { "TIMERSTART", 7, starttimer },
932         { "TIMERCLEAR", 7, cleartimer },
933         { "ONEVENT", 8, onevent },
934         /* 9 - Subroutine label, treated specially */
935         { "SETFLAG", 10, setflag },
936         { "CLEARFLAG", 10, clearflag },
937         { "DELAY", 11, send_delay },
938         { "EXIT", 12 },
939 };
940
941
942 static int process_returncode(struct adsi_soft_key *key, char *code, char *args, struct adsi_script *state, char *script, int lineno)
943 {
944         int x, res;
945         char *unused;
946
947         for (x = 0; x < sizeof(kcmds) / sizeof(kcmds[0]); x++) {
948                 if ((kcmds[x].id > -1) && !strcasecmp(kcmds[x].name, code)) {
949                         if (kcmds[x].add_args) {
950                                 res = kcmds[x].add_args(key->retstr + key->retstrlen,
951                                                 code, kcmds[x].id, args, state, script, lineno);
952                                 if ((key->retstrlen + res - key->initlen) <= MAX_RET_CODE) 
953                                         key->retstrlen += res;
954                                 else 
955                                         ast_log(LOG_WARNING, "No space for '%s' code in key '%s' at line %d of %s\n", kcmds[x].name, key->vname, lineno, script);
956                         } else {
957                                 if ((unused = get_token(&args, script, lineno))) 
958                                         ast_log(LOG_WARNING, "'%s' takes no arguments at line %d of %s (token is '%s')\n", kcmds[x].name, lineno, script, unused);
959                                 if ((key->retstrlen + 1 - key->initlen) <= MAX_RET_CODE) {
960                                         key->retstr[key->retstrlen] = kcmds[x].id;
961                                         key->retstrlen++;
962                                 } else 
963                                         ast_log(LOG_WARNING, "No space for '%s' code in key '%s' at line %d of %s\n", kcmds[x].name, key->vname, lineno, script);
964                         }
965                         return 0;
966                 }
967         }
968         return -1;
969 }
970
971 static int process_opcode(struct adsi_subscript *sub, char *code, char *args, struct adsi_script *state, char *script, int lineno)
972 {
973         int x, res, max = sub->id ? MAX_SUB_LEN : MAX_MAIN_LEN;
974         char *unused;
975
976         for (x = 0; x < sizeof(opcmds) / sizeof(opcmds[0]); x++) {
977                 if ((opcmds[x].id > -1) && !strcasecmp(opcmds[x].name, code)) {
978                         if (opcmds[x].add_args) {
979                                 res = opcmds[x].add_args(sub->data + sub->datalen,
980                                                 code, opcmds[x].id, args, state, script, lineno);
981                                 if ((sub->datalen + res + 1) <= max) 
982                                         sub->datalen += res;
983                                 else {
984                                         ast_log(LOG_WARNING, "No space for '%s' code in subscript '%s' at line %d of %s\n", opcmds[x].name, sub->vname, lineno, script);
985                                         return -1;
986                                 }
987                         } else {
988                                 if ((unused = get_token(&args, script, lineno))) 
989                                         ast_log(LOG_WARNING, "'%s' takes no arguments at line %d of %s (token is '%s')\n", opcmds[x].name, lineno, script, unused);
990                                 if ((sub->datalen + 2) <= max) {
991                                         sub->data[sub->datalen] = opcmds[x].id;
992                                         sub->datalen++;
993                                 } else {
994                                         ast_log(LOG_WARNING, "No space for '%s' code in key '%s' at line %d of %s\n", opcmds[x].name, sub->vname, lineno, script);
995                                         return -1;
996                                 }
997                         }
998                         /* Separate commands with 0xff */
999                         sub->data[sub->datalen] = 0xff;
1000                         sub->datalen++;
1001                         sub->inscount++;
1002                         return 0;
1003                 }
1004         }
1005         return -1;
1006 }
1007
1008 static int adsi_process(struct adsi_script *state, char *buf, char *script, int lineno)
1009 {
1010         char *keyword = get_token(&buf, script, lineno);
1011         char *args, vname[256], tmp[80], tmp2[80];
1012         int lrci, wi, event;
1013         struct adsi_display *disp;
1014         struct adsi_subscript *newsub;
1015
1016         if (!keyword) 
1017                 return 0;
1018
1019         switch(state->state) {
1020         case STATE_NORMAL:
1021                 if (!strcasecmp(keyword, "DESCRIPTION")) {
1022                         if ((args = get_token(&buf, script, lineno))) {
1023                                 if (process_token(state->desc, args, sizeof(state->desc) - 1, ARG_STRING))
1024                                         ast_log(LOG_WARNING, "'%s' is not a valid token for DESCRIPTION at line %d of %s\n", args, lineno, script);
1025                         } else
1026                                 ast_log(LOG_WARNING, "Missing argument for DESCRIPTION at line %d of %s\n", lineno, script);
1027                 } else if (!strcasecmp(keyword, "VERSION")) {
1028                         if ((args = get_token(&buf, script, lineno))) {
1029                                 if (process_token(&state->ver, args, sizeof(state->ver) - 1, ARG_NUMBER))
1030                                         ast_log(LOG_WARNING, "'%s' is not a valid token for VERSION at line %d of %s\n", args, lineno, script);
1031                         } else
1032                                 ast_log(LOG_WARNING, "Missing argument for VERSION at line %d of %s\n", lineno, script);
1033                 } else if (!strcasecmp(keyword, "SECURITY")) {
1034                         if ((args = get_token(&buf, script, lineno))) {
1035                                 if (process_token(state->sec, args, sizeof(state->sec) - 1, ARG_STRING | ARG_NUMBER))
1036                                         ast_log(LOG_WARNING, "'%s' is not a valid token for SECURITY at line %d of %s\n", args, lineno, script);
1037                         } else
1038                                 ast_log(LOG_WARNING, "Missing argument for SECURITY at line %d of %s\n", lineno, script);
1039                 } else if (!strcasecmp(keyword, "FDN")) {
1040                         if ((args = get_token(&buf, script, lineno))) {
1041                                 if (process_token(state->fdn, args, sizeof(state->fdn) - 1, ARG_STRING | ARG_NUMBER))
1042                                         ast_log(LOG_WARNING, "'%s' is not a valid token for FDN at line %d of %s\n", args, lineno, script);
1043                         } else
1044                                 ast_log(LOG_WARNING, "Missing argument for FDN at line %d of %s\n", lineno, script);
1045                 } else if (!strcasecmp(keyword, "KEY")) {
1046                         if (!(args = get_token(&buf, script, lineno))) {
1047                                 ast_log(LOG_WARNING, "KEY definition missing name at line %d of %s\n", lineno, script);
1048                                 break;
1049                         }
1050                         if (process_token(vname, args, sizeof(vname) - 1, ARG_STRING)) {
1051                                 ast_log(LOG_WARNING, "'%s' is not a valid token for a KEY name at line %d of %s\n", args, lineno, script);
1052                                 break;
1053                         }
1054                         if (!(state->key = getkeybyname(state, vname, script, lineno))) {
1055                                 ast_log(LOG_WARNING, "Out of key space at line %d of %s\n", lineno, script);
1056                                 break;
1057                         }
1058                         if (state->key->defined) {
1059                                 ast_log(LOG_WARNING, "Cannot redefine key '%s' at line %d of %s\n", vname, lineno, script);
1060                                 break;
1061                         }
1062                         if (!(args = get_token(&buf, script, lineno)) || strcasecmp(args, "IS")) {
1063                                 ast_log(LOG_WARNING, "Expecting 'IS', but got '%s' at line %d of %s\n", args ? args : "<nothing>", lineno, script);
1064                                 break;
1065                         }
1066                         if (!(args = get_token(&buf, script, lineno))) {
1067                                 ast_log(LOG_WARNING, "KEY definition missing short name at line %d of %s\n", lineno, script);
1068                                 break;
1069                         }
1070                         if (process_token(tmp, args, sizeof(tmp) - 1, ARG_STRING)) {
1071                                 ast_log(LOG_WARNING, "'%s' is not a valid token for a KEY short name at line %d of %s\n", args, lineno, script);
1072                                 break;
1073                         }
1074                         if ((args = get_token(&buf, script, lineno))) {
1075                                 if (strcasecmp(args, "OR")) {
1076                                         ast_log(LOG_WARNING, "Expecting 'OR' but got '%s' instead at line %d of %s\n", args, lineno, script);
1077                                         break;
1078                                 }
1079                                 if (!(args = get_token(&buf, script, lineno))) {
1080                                         ast_log(LOG_WARNING, "KEY definition missing optional long name at line %d of %s\n", lineno, script);
1081                                         break;
1082                                 }
1083                                 if (process_token(tmp2, args, sizeof(tmp2) - 1, ARG_STRING)) {
1084                                         ast_log(LOG_WARNING, "'%s' is not a valid token for a KEY long name at line %d of %s\n", args, lineno, script);
1085                                         break;
1086                                 }
1087                         } else {
1088                                 ast_copy_string(tmp2, tmp, sizeof(tmp2));
1089                         }
1090                         if (strlen(tmp2) > 18) {
1091                                 ast_log(LOG_WARNING, "Truncating full name to 18 characters at line %d of %s\n", lineno, script);
1092                                 tmp2[18] = '\0';
1093                         }
1094                         if (strlen(tmp) > 7) {
1095                                 ast_log(LOG_WARNING, "Truncating short name to 7 bytes at line %d of %s\n", lineno, script);
1096                                 tmp[7] = '\0';
1097                         }
1098                         /* Setup initial stuff */
1099                         state->key->retstr[0] = 128;
1100                         /* 1 has the length */
1101                         state->key->retstr[2] = state->key->id;
1102                         /* Put the Full name in */
1103                         memcpy(state->key->retstr + 3, tmp2, strlen(tmp2));
1104                         /* Update length */
1105                         state->key->retstrlen = strlen(tmp2) + 3;
1106                         /* Put trailing 0xff */
1107                         state->key->retstr[state->key->retstrlen++] = 0xff;
1108                         /* Put the short name */
1109                         memcpy(state->key->retstr + state->key->retstrlen, tmp, strlen(tmp));
1110                         /* Update length */
1111                         state->key->retstrlen += strlen(tmp);
1112                         /* Put trailing 0xff */
1113                         state->key->retstr[state->key->retstrlen++] = 0xff;
1114                         /* Record initial length */
1115                         state->key->initlen = state->key->retstrlen;
1116                         state->state = STATE_INKEY;
1117                 } else if (!strcasecmp(keyword, "SUB")) {
1118                         if (!(args = get_token(&buf, script, lineno))) {
1119                                 ast_log(LOG_WARNING, "SUB definition missing name at line %d of %s\n", lineno, script);
1120                                 break;
1121                         }
1122                         if (process_token(vname, args, sizeof(vname) - 1, ARG_STRING)) {
1123                                 ast_log(LOG_WARNING, "'%s' is not a valid token for a KEY name at line %d of %s\n", args, lineno, script);
1124                                 break;
1125                         }
1126                         if (!(state->sub = getsubbyname(state, vname, script, lineno))) {
1127                                 ast_log(LOG_WARNING, "Out of subroutine space at line %d of %s\n", lineno, script);
1128                                 break;
1129                         }
1130                         if (state->sub->defined) {
1131                                 ast_log(LOG_WARNING, "Cannot redefine subroutine '%s' at line %d of %s\n", vname, lineno, script);
1132                                 break;
1133                         }
1134                         /* Setup sub */
1135                         state->sub->data[0] = 130;
1136                         /* 1 is the length */
1137                         state->sub->data[2] = 0x0; /* Clear extensibility bit */
1138                         state->sub->datalen = 3;
1139                         if (state->sub->id) {
1140                                 /* If this isn't the main subroutine, make a subroutine label for it */
1141                                 state->sub->data[3] = 9;
1142                                 state->sub->data[4] = state->sub->id;
1143                                 /* 5 is length */
1144                                 state->sub->data[6] = 0xff;
1145                                 state->sub->datalen = 7;
1146                         }
1147                         if (!(args = get_token(&buf, script, lineno)) || strcasecmp(args, "IS")) {
1148                                 ast_log(LOG_WARNING, "Expecting 'IS', but got '%s' at line %d of %s\n", args ? args : "<nothing>", lineno, script);
1149                                 break;
1150                         }
1151                         state->state = STATE_INSUB;
1152                 } else if (!strcasecmp(keyword, "STATE")) {
1153                         if (!(args = get_token(&buf, script, lineno))) {
1154                                 ast_log(LOG_WARNING, "STATE definition missing name at line %d of %s\n", lineno, script);
1155                                 break;
1156                         }
1157                         if (process_token(vname, args, sizeof(vname) - 1, ARG_STRING)) {
1158                                 ast_log(LOG_WARNING, "'%s' is not a valid token for a STATE name at line %d of %s\n", args, lineno, script);
1159                                 break;
1160                         }
1161                         if (getstatebyname(state, vname, script, lineno, 0)) {
1162                                 ast_log(LOG_WARNING, "State '%s' is already defined at line %d of %s\n", vname, lineno, script);
1163                                 break;
1164                         }
1165                         getstatebyname(state, vname, script, lineno, 1);
1166                 } else if (!strcasecmp(keyword, "FLAG")) {
1167                         if (!(args = get_token(&buf, script, lineno))) {
1168                                 ast_log(LOG_WARNING, "FLAG definition missing name at line %d of %s\n", lineno, script);
1169                                 break;
1170                         }
1171                         if (process_token(vname, args, sizeof(vname) - 1, ARG_STRING)) {
1172                                 ast_log(LOG_WARNING, "'%s' is not a valid token for a FLAG name at line %d of %s\n", args, lineno, script);
1173                                 break;
1174                         }
1175                         if (getflagbyname(state, vname, script, lineno, 0)) {
1176                                 ast_log(LOG_WARNING, "Flag '%s' is already defined\n", vname);
1177                                 break;
1178                         }
1179                         getflagbyname(state, vname, script, lineno, 1);
1180                 } else if (!strcasecmp(keyword, "DISPLAY")) {
1181                         lrci = 0;
1182                         wi = 0;
1183                         if (!(args = get_token(&buf, script, lineno))) {
1184                                 ast_log(LOG_WARNING, "SUB definition missing name at line %d of %s\n", lineno, script);
1185                                 break;
1186                         }
1187                         if (process_token(vname, args, sizeof(vname) - 1, ARG_STRING)) {
1188                                 ast_log(LOG_WARNING, "'%s' is not a valid token for a KEY name at line %d of %s\n", args, lineno, script);
1189                                 break;
1190                         }
1191                         if (getdisplaybyname(state, vname, script, lineno, 0)) {
1192                                 ast_log(LOG_WARNING, "State '%s' is already defined\n", vname);
1193                                 break;
1194                         }
1195                         if (!(disp = getdisplaybyname(state, vname, script, lineno, 1)))
1196                                 break;
1197                         if (!(args = get_token(&buf, script, lineno)) || strcasecmp(args, "IS")) {
1198                                 ast_log(LOG_WARNING, "Missing 'IS' at line %d of %s\n", lineno, script);
1199                                 break;
1200                         }
1201                         if (!(args = get_token(&buf, script, lineno))) {
1202                                 ast_log(LOG_WARNING, "Missing Column 1 text at line %d of %s\n", lineno, script);
1203                                 break;
1204                         }
1205                         if (process_token(tmp, args, sizeof(tmp) - 1, ARG_STRING)) {
1206                                 ast_log(LOG_WARNING, "Token '%s' is not valid column 1 text at line %d of %s\n", args, lineno, script);
1207                                 break;
1208                         }
1209                         if (strlen(tmp) > 20) {
1210                                 ast_log(LOG_WARNING, "Truncating column one to 20 characters at line %d of %s\n", lineno, script);
1211                                 tmp[20] = '\0';
1212                         }
1213                         memcpy(disp->data + 5, tmp, strlen(tmp));
1214                         disp->datalen = strlen(tmp) + 5;
1215                         disp->data[disp->datalen++] = 0xff;
1216
1217                         args = get_token(&buf, script, lineno);
1218                         if (args && !process_token(tmp, args, sizeof(tmp) - 1, ARG_STRING)) {
1219                                 /* Got a column two */
1220                                 if (strlen(tmp) > 20) {
1221                                         ast_log(LOG_WARNING, "Truncating column two to 20 characters at line %d of %s\n", lineno, script);
1222                                         tmp[20] = '\0';
1223                                 }
1224                                 memcpy(disp->data + disp->datalen, tmp, strlen(tmp));
1225                                 disp->datalen += strlen(tmp);
1226                                 args = get_token(&buf, script, lineno);
1227                         }
1228                         while(args) {
1229                                 if (!strcasecmp(args, "JUSTIFY")) {
1230                                         args = get_token(&buf, script, lineno);
1231                                         if (!args) {
1232                                                 ast_log(LOG_WARNING, "Qualifier 'JUSTIFY' requires an argument at line %d of %s\n", lineno, script);
1233                                                 break;
1234                                         }
1235                                         lrci = getjustifybyname(args);
1236                                         if (lrci < 0) {
1237                                                 ast_log(LOG_WARNING, "'%s' is not a valid justification at line %d of %s\n", args, lineno, script);
1238                                                 break;
1239                                         }
1240                                 } else if (!strcasecmp(args, "WRAP")) {
1241                                         wi = 0x80;
1242                                 } else {
1243                                         ast_log(LOG_WARNING, "'%s' is not a known qualifier at line %d of %s\n", args, lineno, script);
1244                                         break;
1245                                 }
1246                                 args = get_token(&buf, script, lineno);
1247                         }
1248                         if (args) {
1249                                 /* Something bad happened */
1250                                 break;
1251                         }
1252                         disp->data[0] = 129;
1253                         disp->data[1] = disp->datalen - 2;
1254                         disp->data[2] = ((lrci & 0x3) << 6) | disp->id;
1255                         disp->data[3] = wi;
1256                         disp->data[4] = 0xff;
1257                 } else {
1258                         ast_log(LOG_WARNING, "Invalid or Unknown keyword '%s' in PROGRAM\n", keyword);
1259                 }
1260                 break;
1261         case STATE_INKEY:
1262                 if (process_returncode(state->key, keyword, buf, state, script, lineno)) {
1263                         if (!strcasecmp(keyword, "ENDKEY")) {
1264                                 /* Return to normal operation and increment current key */
1265                                 state->state = STATE_NORMAL;
1266                                 state->key->defined = 1;
1267                                 state->key->retstr[1] = state->key->retstrlen - 2;
1268                                 state->key = NULL;
1269                         } else {
1270                                 ast_log(LOG_WARNING, "Invalid or Unknown keyword '%s' in SOFTKEY definition at line %d of %s\n", keyword, lineno, script);
1271                         }
1272                 }
1273                 break;
1274         case STATE_INIF:
1275                 if (process_opcode(state->sub, keyword, buf, state, script, lineno)) {
1276                         if (!strcasecmp(keyword, "ENDIF")) {
1277                                 /* Return to normal SUB operation and increment current key */
1278                                 state->state = STATE_INSUB;
1279                                 state->sub->defined = 1;
1280                                 /* Store the proper number of instructions */
1281                                 state->sub->ifdata[2] = state->sub->ifinscount;
1282                         } else if (!strcasecmp(keyword, "GOTO")) {
1283                                 if (!(args = get_token(&buf, script, lineno))) {
1284                                         ast_log(LOG_WARNING, "GOTO clause missing Subscript name at line %d of %s\n", lineno, script);
1285                                         break;
1286                                 }
1287                                 if (process_token(tmp, args, sizeof(tmp) - 1, ARG_STRING)) {
1288                                         ast_log(LOG_WARNING, "'%s' is not a valid subscript name token at line %d of %s\n", args, lineno, script);
1289                                         break;
1290                                 }
1291                                 if (!(newsub = getsubbyname(state, tmp, script, lineno)))
1292                                         break;
1293                                 /* Somehow you use GOTO to go to another place */
1294                                 state->sub->data[state->sub->datalen++] = 0x8;
1295                                 state->sub->data[state->sub->datalen++] = state->sub->ifdata[1];
1296                                 state->sub->data[state->sub->datalen++] = newsub->id;
1297                                 /* Terminate */
1298                                 state->sub->data[state->sub->datalen++] = 0xff;
1299                                 /* Increment counters */
1300                                 state->sub->inscount++;
1301                                 state->sub->ifinscount++;
1302                         } else {
1303                                 ast_log(LOG_WARNING, "Invalid or Unknown keyword '%s' in IF clause at line %d of %s\n", keyword, lineno, script);
1304                         }
1305                 } else
1306                         state->sub->ifinscount++;
1307                 break;
1308         case STATE_INSUB:
1309                 if (process_opcode(state->sub, keyword, buf, state, script, lineno)) {
1310                         if (!strcasecmp(keyword, "ENDSUB")) {
1311                                 /* Return to normal operation and increment current key */
1312                                 state->state = STATE_NORMAL;
1313                                 state->sub->defined = 1;
1314                                 /* Store the proper length */
1315                                 state->sub->data[1] = state->sub->datalen - 2;
1316                                 if (state->sub->id) {
1317                                         /* if this isn't main, store number of instructions, too */
1318                                         state->sub->data[5] = state->sub->inscount;
1319                                 }
1320                                 state->sub = NULL;
1321                         } else if (!strcasecmp(keyword, "IFEVENT")) {
1322                                 if (!(args = get_token(&buf, script, lineno))) {
1323                                         ast_log(LOG_WARNING, "IFEVENT clause missing Event name at line %d of %s\n", lineno, script);
1324                                         break;
1325                                 }
1326                                 if ((event = geteventbyname(args)) < 1) {
1327                                         ast_log(LOG_WARNING, "'%s' is not a valid event\n", args);
1328                                         break;
1329                                 }
1330                                 if (!(args = get_token(&buf, script, lineno)) || strcasecmp(args, "THEN")) {
1331                                         ast_log(LOG_WARNING, "IFEVENT clause missing 'THEN' at line %d of %s\n", lineno, script);
1332                                         break;
1333                                 }
1334                                 state->sub->ifinscount = 0;
1335                                 state->sub->ifdata = state->sub->data + 
1336                                                 state->sub->datalen;
1337                                 /* Reserve header and insert op codes */
1338                                 state->sub->ifdata[0] = 0x1;
1339                                 state->sub->ifdata[1] = event;
1340                                 /* 2 is for the number of instructions */
1341                                 state->sub->ifdata[3] = 0xff;
1342                                 state->sub->datalen += 4;
1343                                 /* Update Subscript instruction count */
1344                                 state->sub->inscount++;
1345                                 state->state = STATE_INIF;
1346                         } else {
1347                                 ast_log(LOG_WARNING, "Invalid or Unknown keyword '%s' in SUB definition at line %d of %s\n", keyword, lineno, script);
1348                         }
1349                 }
1350                 break;
1351         default:
1352                 ast_log(LOG_WARNING, "Can't process keyword '%s' in weird state %d\n", keyword, state->state);
1353         }
1354         return 0;
1355 }
1356
1357 static struct adsi_script *compile_script(char *script)
1358 {
1359         FILE *f;
1360         char fn[256], buf[256], *c;
1361         int lineno = 0, x, err;
1362         struct adsi_script *scr;
1363
1364         if (script[0] == '/')
1365                 ast_copy_string(fn, script, sizeof(fn));
1366         else
1367                 snprintf(fn, sizeof(fn), "%s/%s", (char *)ast_config_AST_CONFIG_DIR, script);
1368
1369         if (!(f = fopen(fn, "r"))) {
1370                 ast_log(LOG_WARNING, "Can't open file '%s'\n", fn);
1371                 return NULL;
1372         }
1373
1374         if (!(scr = ast_calloc(1, sizeof(*scr)))) {
1375                 fclose(f);
1376                 return NULL;
1377         }
1378
1379         /* Create "main" as first subroutine */
1380         getsubbyname(scr, "main", NULL, 0);
1381         while(!feof(f)) {
1382                 fgets(buf, sizeof(buf), f);
1383                 if (!feof(f)) {
1384                         lineno++;
1385                         /* Trim off trailing return */
1386                         buf[strlen(buf) - 1] = '\0';
1387                         /* Strip comments */
1388                         if ((c = strchr(buf, ';')))
1389                                 *c = '\0';
1390                         if (!ast_strlen_zero(buf))
1391                                 adsi_process(scr, buf, script, lineno);
1392                 }
1393         }
1394         fclose(f);
1395         /* Make sure we're in the main routine again */
1396         switch(scr->state) {
1397         case STATE_NORMAL:
1398                 break;
1399         case STATE_INSUB:
1400                 ast_log(LOG_WARNING, "Missing ENDSUB at end of file %s\n", script);
1401                 ast_free(scr);
1402                 return NULL;
1403         case STATE_INKEY:
1404                 ast_log(LOG_WARNING, "Missing ENDKEY at end of file %s\n", script);
1405                 ast_free(scr);
1406                 return NULL;
1407         }
1408         err = 0;
1409
1410         /* Resolve all keys and record their lengths */
1411         for (x = 0; x < scr->numkeys; x++) {
1412                 if (!scr->keys[x].defined) {
1413                         ast_log(LOG_WARNING, "Key '%s' referenced but never defined in file %s\n", scr->keys[x].vname, fn);
1414                         err++;
1415                 }
1416         }
1417
1418         /* Resolve all subs */
1419         for (x = 0; x < scr->numsubs; x++) {
1420                 if (!scr->subs[x].defined) {
1421                         ast_log(LOG_WARNING, "Subscript '%s' referenced but never defined in file %s\n", scr->subs[x].vname, fn);
1422                         err++;
1423                 }
1424                 if (x == (scr->numsubs - 1)) {
1425                         /* Clear out extension bit on last message */
1426                         scr->subs[x].data[2] = 0x80;
1427                 }
1428         }
1429
1430         if (err) {
1431                 ast_free(scr);
1432                 return NULL;
1433         }
1434         return scr;
1435 }
1436
1437 #ifdef DUMP_MESSAGES
1438 static void dump_message(char *type, char *vname, unsigned char *buf, int buflen)
1439 {
1440         int x;
1441         printf("%s %s: [ ", type, vname);
1442         for (x = 0; x < buflen; x++)
1443                 printf("%02x ", buf[x]);
1444         printf("]\n");
1445 }
1446 #endif
1447
1448 static int adsi_prog(struct ast_channel *chan, char *script)
1449 {
1450         struct adsi_script *scr;
1451         int x, bytes;
1452         unsigned char buf[1024];
1453
1454         if (!(scr = compile_script(script)))
1455                 return -1;
1456
1457         /* Start an empty ADSI Session */
1458         if (ast_adsi_load_session(chan, NULL, 0, 1) < 1) 
1459                 return -1;
1460
1461         /* Now begin the download attempt */
1462         if (ast_adsi_begin_download(chan, scr->desc, scr->fdn, scr->sec, scr->ver)) {
1463                 /* User rejected us for some reason */
1464                 ast_verb(3, "User rejected download attempt\n");
1465                 ast_log(LOG_NOTICE, "User rejected download on channel %s\n", chan->name);
1466                 ast_free(scr);
1467                 return -1;
1468         }
1469
1470         bytes = 0;
1471         /* Start with key definitions */
1472         for (x = 0; x < scr->numkeys; x++) {
1473                 if (bytes + scr->keys[x].retstrlen > 253) {
1474                         /* Send what we've collected so far */
1475                         if (ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD)) {
1476                                 ast_log(LOG_WARNING, "Unable to send chunk ending at %d\n", x);
1477                                 return -1;
1478                         }
1479                         bytes =0;
1480                 }
1481                 memcpy(buf + bytes, scr->keys[x].retstr, scr->keys[x].retstrlen);
1482                 bytes += scr->keys[x].retstrlen;
1483 #ifdef DUMP_MESSAGES
1484                 dump_message("Key", scr->keys[x].vname, scr->keys[x].retstr, scr->keys[x].retstrlen);
1485 #endif
1486         }
1487         if (bytes) {
1488                 if (ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD)) {
1489                         ast_log(LOG_WARNING, "Unable to send chunk ending at %d\n", x);
1490                         return -1;
1491                 }
1492         }
1493
1494         bytes = 0;
1495         /* Continue with the display messages */
1496         for (x = 0; x < scr->numdisplays; x++) {
1497                 if (bytes + scr->displays[x].datalen > 253) {
1498                         /* Send what we've collected so far */
1499                         if (ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD)) {
1500                                 ast_log(LOG_WARNING, "Unable to send chunk ending at %d\n", x);
1501                                 return -1;
1502                         }
1503                         bytes =0;
1504                 }
1505                 memcpy(buf + bytes, scr->displays[x].data, scr->displays[x].datalen);
1506                 bytes += scr->displays[x].datalen;
1507 #ifdef DUMP_MESSAGES
1508                 dump_message("Display", scr->displays[x].vname, scr->displays[x].data, scr->displays[x].datalen);
1509 #endif
1510         }
1511         if (bytes) {
1512                 if (ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD)) {
1513                         ast_log(LOG_WARNING, "Unable to send chunk ending at %d\n", x);
1514                         return -1;
1515                 }
1516         }
1517
1518         bytes = 0;
1519         /* Send subroutines */
1520         for (x = 0; x < scr->numsubs; x++) {
1521                 if (bytes + scr->subs[x].datalen > 253) {
1522                         /* Send what we've collected so far */
1523                         if (ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD)) {
1524                                 ast_log(LOG_WARNING, "Unable to send chunk ending at %d\n", x);
1525                                 return -1;
1526                         }
1527                         bytes =0;
1528                 }
1529                 memcpy(buf + bytes, scr->subs[x].data, scr->subs[x].datalen);
1530                 bytes += scr->subs[x].datalen;
1531 #ifdef DUMP_MESSAGES
1532                 dump_message("Sub", scr->subs[x].vname, scr->subs[x].data, scr->subs[x].datalen);
1533 #endif
1534         }
1535         if (bytes) {
1536                 if (ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD)) {
1537                         ast_log(LOG_WARNING, "Unable to send chunk ending at %d\n", x);
1538                         return -1;
1539                 }
1540         }
1541
1542
1543         bytes = 0;
1544         bytes += ast_adsi_display(buf, ADSI_INFO_PAGE, 1, ADSI_JUST_LEFT, 0, "Download complete.", "");
1545         bytes += ast_adsi_set_line(buf, ADSI_INFO_PAGE, 1);
1546         if (ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY) < 0)
1547                 return -1;
1548         if (ast_adsi_end_download(chan)) {
1549                 /* Download failed for some reason */
1550                 ast_verb(3, "Download attempt failed\n");
1551                 ast_log(LOG_NOTICE, "Download failed on %s\n", chan->name);
1552                 ast_free(scr);
1553                 return -1;
1554         }
1555         ast_free(scr);
1556         ast_adsi_unload_session(chan);
1557         return 0;
1558 }
1559
1560 static int adsi_exec(struct ast_channel *chan, void *data)
1561 {
1562         int res = 0;
1563         
1564         if (ast_strlen_zero(data))
1565                 data = "asterisk.adsi";
1566         
1567         if (!ast_adsi_available(chan)) {
1568                 ast_verb(3, "ADSI Unavailable on CPE.  Not bothering to try.\n");
1569         } else {
1570                 ast_verb(3, "ADSI Available on CPE.  Attempting Upload.\n");
1571                 res = adsi_prog(chan, data);
1572         }
1573         
1574         return res;
1575 }
1576
1577 static int unload_module(void)
1578 {
1579         return ast_unregister_application(app);
1580 }
1581
1582 static int load_module(void)
1583 {
1584         return ast_register_application(app, adsi_exec, synopsis, descrip);
1585 }
1586
1587 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Asterisk ADSI Programming Application");