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