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