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