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