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