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