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