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