Fix timestamp to always be unsigned
[asterisk/asterisk.git] / channels / iax2-parser.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * Implementation of Inter-Asterisk eXchange
5  * 
6  * Copyright (C) 2003, Digium
7  *
8  * Mark Spencer <markster@linux-support.net>
9  *
10  * This program is free software, distributed under the terms of
11  * the GNU General Public License
12  */
13
14 #include <sys/types.h>
15 #include <sys/socket.h>
16 #include <string.h>
17 #include <netinet/in.h>
18 #include <asterisk/frame.h>
19 #include <arpa/inet.h>
20 #include <unistd.h>
21 #include <stdlib.h>
22 #include <stdio.h>
23 #include "iax2.h"
24 #include "iax2-parser.h"
25
26
27 static int frames = 0;
28 static int iframes = 0;
29 static int oframes = 0;
30
31 static void internaloutput(const char *str)
32 {
33         printf(str);
34 }
35
36 static void internalerror(const char *str)
37 {
38         fprintf(stderr, "WARNING: %s", str);
39 }
40
41 static void (*outputf)(const char *str) = internaloutput;
42 static void (*errorf)(const char *str) = internalerror;
43
44 static void dump_addr(char *output, int maxlen, void *value, int len)
45 {
46         struct sockaddr_in sin;
47         if (len == sizeof(sin)) {
48                 memcpy(&sin, value, len);
49                 snprintf(output, maxlen, "IPV4 %s:%d", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
50         } else {
51                 snprintf(output, maxlen, "Invalid Address");
52         }
53 }
54
55 static void dump_string(char *output, int maxlen, void *value, int len)
56 {
57         maxlen--;
58         if (maxlen > len)
59                 maxlen = len;
60         strncpy(output,value, maxlen);
61         output[maxlen] = '\0';
62 }
63
64 static void dump_int(char *output, int maxlen, void *value, int len)
65 {
66         if (len == sizeof(unsigned int))
67                 snprintf(output, maxlen, "%ld", (long)ntohl(*((unsigned int *)value)));
68         else
69                 snprintf(output, maxlen, "Invalid INT");
70 }
71
72 static void dump_short(char *output, int maxlen, void *value, int len)
73 {
74         if (len == sizeof(unsigned short))
75                 snprintf(output, maxlen, "%d", ntohs(*((unsigned short *)value)));
76         else
77                 snprintf(output, maxlen, "Invalid SHORT");
78 }
79
80 static void dump_byte(char *output, int maxlen, void *value, int len)
81 {
82         if (len == sizeof(unsigned char))
83                 snprintf(output, maxlen, "%d", ntohs(*((unsigned char *)value)));
84         else
85                 snprintf(output, maxlen, "Invalid BYTE");
86 }
87
88 static struct iax2_ie {
89         int ie;
90         char *name;
91         void (*dump)(char *output, int maxlen, void *value, int len);
92 } ies[] = {
93         { IAX_IE_CALLED_NUMBER, "CALLED NUMBER", dump_string },
94         { IAX_IE_CALLING_NUMBER, "CALLING NUMBER", dump_string },
95         { IAX_IE_CALLING_ANI, "ANI", dump_string },
96         { IAX_IE_CALLING_NAME, "CALLING NAME", dump_string },
97         { IAX_IE_CALLED_CONTEXT, "CALLED CONTEXT", dump_string },
98         { IAX_IE_USERNAME, "USERNAME", dump_string },
99         { IAX_IE_PASSWORD, "PASSWORD", dump_string },
100         { IAX_IE_CAPABILITY, "CAPABILITY", dump_int },
101         { IAX_IE_FORMAT, "FORMAT", dump_int },
102         { IAX_IE_LANGUAGE, "LANGUAGE", dump_string },
103         { IAX_IE_VERSION, "VERSION", dump_short },
104         { IAX_IE_ADSICPE, "ADSICPE", dump_short },
105         { IAX_IE_DNID, "DNID", dump_string },
106         { IAX_IE_AUTHMETHODS, "AUTHMETHODS", dump_short },
107         { IAX_IE_CHALLENGE, "CHALLENGE", dump_string },
108         { IAX_IE_MD5_RESULT, "MD5 RESULT", dump_string },
109         { IAX_IE_RSA_RESULT, "RSA RESULT", dump_string },
110         { IAX_IE_APPARENT_ADDR, "APPARENT ADDRESS", dump_addr },
111         { IAX_IE_REFRESH, "REFRESH", dump_short },
112         { IAX_IE_DPSTATUS, "DIALPLAN STATUS", dump_short },
113         { IAX_IE_CALLNO, "CALL NUMBER", dump_short },
114         { IAX_IE_CAUSE, "CAUSE", dump_string },
115         { IAX_IE_IAX_UNKNOWN, "UNKNOWN IAX CMD", dump_byte },
116         { IAX_IE_MSGCOUNT, "MESSAGE COUNT", dump_short },
117         { IAX_IE_AUTOANSWER, "AUTO ANSWER REQ" },
118         { IAX_IE_TRANSFERID, "TRANSFER ID", dump_int },
119         { IAX_IE_RDNIS, "REFERRING DNIS", dump_string },
120         { IAX_IE_PROVISIONING, "PROVISIONING" },
121         { IAX_IE_AESPROVISIONING, "AES PROVISIONING" },
122         { IAX_IE_DATETIME, "DATE TIME", dump_int },
123         { IAX_IE_DEVICETYPE, "DEVICE TYPE", dump_string },
124         { IAX_IE_SERVICEIDENT, "SERVICE IDENT", dump_string },
125         { IAX_IE_FIRMWAREVER, "FIRMWARE VER", dump_short },
126         { IAX_IE_FWBLOCKDESC, "FW BLOCK DESC", dump_int },
127         { IAX_IE_FWBLOCKDATA, "FW BLOCK DATA" },
128 };
129
130 const char *iax_ie2str(int ie)
131 {
132         int x;
133         for (x=0;x<sizeof(ies) / sizeof(ies[0]); x++) {
134                 if (ies[x].ie == ie)
135                         return ies[x].name;
136         }
137         return "Unknown IE";
138 }
139
140 static void dump_ies(unsigned char *iedata, int len)
141 {
142         int ielen;
143         int ie;
144         int x;
145         int found;
146         char interp[80];
147         char tmp[256];
148         if (len < 2)
149                 return;
150         while(len > 2) {
151                 ie = iedata[0];
152                 ielen = iedata[1];
153                 if (ielen + 2> len) {
154                         snprintf(tmp, sizeof(tmp), "Total IE length of %d bytes exceeds remaining frame length of %d bytes\n", ielen + 2, len);
155                         outputf(tmp);
156                         return;
157                 }
158                 found = 0;
159                 for (x=0;x<sizeof(ies) / sizeof(ies[0]); x++) {
160                         if (ies[x].ie == ie) {
161                                 if (ies[x].dump) {
162                                         ies[x].dump(interp, sizeof(interp), iedata + 2, ielen);
163                                         snprintf(tmp, sizeof(tmp), "   %-15.15s : %s\n", ies[x].name, interp);
164                                         outputf(tmp);
165                                 } else {
166                                         if (ielen)
167                                                 snprintf(interp, sizeof(interp), "%d bytes", ielen);
168                                         else
169                                                 strcpy(interp, "Present");
170                                         snprintf(tmp, sizeof(tmp), "   %-15.15s : %s\n", ies[x].name, interp);
171                                         outputf(tmp);
172                                 }
173                                 found++;
174                         }
175                 }
176                 if (!found) {
177                         snprintf(tmp, sizeof(tmp), "   Unknown IE %03d  : Present\n", ie);
178                         outputf(tmp);
179                 }
180                 iedata += (2 + ielen);
181                 len -= (2 + ielen);
182         }
183         outputf("\n");
184 }
185
186 void iax_showframe(struct iax_frame *f, struct ast_iax2_full_hdr *fhi, int rx, struct sockaddr_in *sin, int datalen)
187 {
188         char *frames[] = {
189                 "(0?)",
190                 "DTMF   ",
191                 "VOICE  ",
192                 "VIDEO  ",
193                 "CONTROL",
194                 "NULL   ",
195                 "IAX    ",
196                 "TEXT   ",
197                 "IMAGE  " };
198         char *iaxs[] = {
199                 "(0?)",
200                 "NEW    ",
201                 "PING   ",
202                 "PONG   ",
203                 "ACK    ",
204                 "HANGUP ",
205                 "REJECT ",
206                 "ACCEPT ",
207                 "AUTHREQ",
208                 "AUTHREP",
209                 "INVAL  ",
210                 "LAGRQ  ",
211                 "LAGRP  ",
212                 "REGREQ ",
213                 "REGAUTH",
214                 "REGACK ",
215                 "REGREJ ",
216                 "REGREL ",
217                 "VNAK   ",
218                 "DPREQ  ",
219                 "DPREP  ",
220                 "DIAL   ",
221                 "TXREQ  ",
222                 "TXCNT  ",
223                 "TXACC  ",
224                 "TXREADY",
225                 "TXREL  ",
226                 "TXREJ  ",
227                 "QUELCH ",
228                 "UNQULCH",
229                 "POKE",
230                 "PAGE",
231                 "MWI",
232                 "UNSUPPORTED",
233                 "TRANSFER",
234                 "PROVISION",
235                 "FWDOWNLD",
236                 "FWDATA"
237         };
238         char *cmds[] = {
239                 "(0?)",
240                 "HANGUP ",
241                 "RING   ",
242                 "RINGING",
243                 "ANSWER ",
244                 "BUSY   ",
245                 "TKOFFHK ",
246                 "OFFHOOK" };
247         struct ast_iax2_full_hdr *fh;
248         char retries[20];
249         char class2[20];
250         char subclass2[20];
251         char *class;
252         char *subclass;
253         char tmp[256];
254         if (f) {
255                 fh = f->data;
256                 snprintf(retries, sizeof(retries), "%03d", f->retries);
257         } else {
258                 fh = fhi;
259                 if (ntohs(fh->dcallno) & IAX_FLAG_RETRANS)
260                         strcpy(retries, "Yes");
261                 else
262                         strcpy(retries, "No");
263         }
264         if (!(ntohs(fh->scallno) & IAX_FLAG_FULL)) {
265                 /* Don't mess with mini-frames */
266                 return;
267         }
268         if (fh->type > sizeof(frames)/sizeof(char *)) {
269                 snprintf(class2, sizeof(class2), "(%d?)", fh->type);
270                 class = class2;
271         } else {
272                 class = frames[(int)fh->type];
273         }
274         if (fh->type == AST_FRAME_DTMF) {
275                 sprintf(subclass2, "%c", fh->csub);
276                 subclass = subclass2;
277         } else if (fh->type == AST_FRAME_IAX) {
278                 if (fh->csub >= sizeof(iaxs)/sizeof(iaxs[0])) {
279                         snprintf(subclass2, sizeof(subclass2), "(%d?)", fh->csub);
280                         subclass = subclass2;
281                 } else {
282                         subclass = iaxs[(int)fh->csub];
283                 }
284         } else if (fh->type == AST_FRAME_CONTROL) {
285                 if (fh->csub > sizeof(cmds)/sizeof(char *)) {
286                         snprintf(subclass2, sizeof(subclass2), "(%d?)", fh->csub);
287                         subclass = subclass2;
288                 } else {
289                         subclass = cmds[(int)fh->csub];
290                 }
291         } else {
292                 snprintf(subclass2, sizeof(subclass2), "%d", fh->csub);
293                 subclass = subclass2;
294         }
295 snprintf(tmp, sizeof(tmp), 
296 "%s-Frame Retry[%s] -- OSeqno: %3.3d ISeqno: %3.3d Type: %s Subclass: %s\n",
297         (rx ? "Rx" : "Tx"),
298         retries, fh->oseqno, fh->iseqno, class, subclass);
299         outputf(tmp);
300 snprintf(tmp, sizeof(tmp), 
301 "   Timestamp: %05lums  SCall: %5.5d  DCall: %5.5d [%s:%d]\n",
302         (unsigned long)ntohl(fh->ts),
303         ntohs(fh->scallno) & ~IAX_FLAG_FULL, ntohs(fh->dcallno) & ~IAX_FLAG_RETRANS,
304                 inet_ntoa(sin->sin_addr), ntohs(sin->sin_port));
305         outputf(tmp);
306         if (fh->type == AST_FRAME_IAX)
307                 dump_ies(fh->iedata, datalen);
308 }
309
310 int iax_ie_append_raw(struct iax_ie_data *ied, unsigned char ie, void *data, int datalen)
311 {
312         char tmp[256];
313         if (datalen > (sizeof(ied->buf) - ied->pos)) {
314                 snprintf(tmp, sizeof(tmp), "Out of space for ie '%s' (%d), need %d have %d\n", iax_ie2str(ie), ie, datalen, sizeof(ied->buf) - ied->pos);
315                 errorf(tmp);
316                 return -1;
317         }
318         ied->buf[ied->pos++] = ie;
319         ied->buf[ied->pos++] = datalen;
320         memcpy(ied->buf + ied->pos, data, datalen);
321         ied->pos += datalen;
322         return 0;
323 }
324
325 int iax_ie_append_addr(struct iax_ie_data *ied, unsigned char ie, struct sockaddr_in *sin)
326 {
327         return iax_ie_append_raw(ied, ie, sin, sizeof(struct sockaddr_in));
328 }
329
330 int iax_ie_append_int(struct iax_ie_data *ied, unsigned char ie, unsigned int value) 
331 {
332         unsigned int newval;
333         newval = htonl(value);
334         return iax_ie_append_raw(ied, ie, &newval, sizeof(newval));
335 }
336
337 int iax_ie_append_short(struct iax_ie_data *ied, unsigned char ie, unsigned short value) 
338 {
339         unsigned short newval;
340         newval = htons(value);
341         return iax_ie_append_raw(ied, ie, &newval, sizeof(newval));
342 }
343
344 int iax_ie_append_str(struct iax_ie_data *ied, unsigned char ie, unsigned char *str)
345 {
346         return iax_ie_append_raw(ied, ie, str, strlen(str));
347 }
348
349 int iax_ie_append_byte(struct iax_ie_data *ied, unsigned char ie, unsigned char dat)
350 {
351         return iax_ie_append_raw(ied, ie, &dat, 1);
352 }
353
354 int iax_ie_append(struct iax_ie_data *ied, unsigned char ie) 
355 {
356         return iax_ie_append_raw(ied, ie, NULL, 0);
357 }
358
359 void iax_set_output(void (*func)(const char *))
360 {
361         outputf = func;
362 }
363
364 void iax_set_error(void (*func)(const char *))
365 {
366         errorf = func;
367 }
368
369 int iax_parse_ies(struct iax_ies *ies, unsigned char *data, int datalen)
370 {
371         /* Parse data into information elements */
372         int len;
373         int ie;
374         char tmp[256];
375         memset(ies, 0, sizeof(struct iax_ies));
376         ies->msgcount = -1;
377         ies->firmwarever = -1;
378         while(datalen >= 2) {
379                 ie = data[0];
380                 len = data[1];
381                 if (len > datalen - 2) {
382                         errorf("Information element length exceeds message size\n");
383                         return -1;
384                 }
385                 switch(ie) {
386                 case IAX_IE_CALLED_NUMBER:
387                         ies->called_number = data + 2;
388                         break;
389                 case IAX_IE_CALLING_NUMBER:
390                         ies->calling_number = data + 2;
391                         break;
392                 case IAX_IE_CALLING_ANI:
393                         ies->calling_ani = data + 2;
394                         break;
395                 case IAX_IE_CALLING_NAME:
396                         ies->calling_name = data + 2;
397                         break;
398                 case IAX_IE_CALLED_CONTEXT:
399                         ies->called_context = data + 2;
400                         break;
401                 case IAX_IE_USERNAME:
402                         ies->username = data + 2;
403                         break;
404                 case IAX_IE_PASSWORD:
405                         ies->password = data + 2;
406                         break;
407                 case IAX_IE_CAPABILITY:
408                         if (len != sizeof(unsigned int)) {
409                                 snprintf(tmp, sizeof(tmp), "Expecting capability to be %d bytes long but was %d\n", sizeof(unsigned int), len);
410                                 errorf(tmp);
411                         } else
412                                 ies->capability = ntohl(*((unsigned int *)(data + 2)));
413                         break;
414                 case IAX_IE_FORMAT:
415                         if (len != sizeof(unsigned int)) {
416                                 snprintf(tmp, sizeof(tmp), "Expecting format to be %d bytes long but was %d\n", sizeof(unsigned int), len);
417                                 errorf(tmp);
418                         } else
419                                 ies->format = ntohl(*((unsigned int *)(data + 2)));
420                         break;
421                 case IAX_IE_LANGUAGE:
422                         ies->language = data + 2;
423                         break;
424                 case IAX_IE_VERSION:
425                         if (len != sizeof(unsigned short)) {
426                                 snprintf(tmp, sizeof(tmp),  "Expecting version to be %d bytes long but was %d\n", sizeof(unsigned short), len);
427                                 errorf(tmp);
428                         } else
429                                 ies->version = ntohs(*((unsigned short *)(data + 2)));
430                         break;
431                 case IAX_IE_ADSICPE:
432                         if (len != sizeof(unsigned short)) {
433                                 snprintf(tmp, sizeof(tmp), "Expecting adsicpe to be %d bytes long but was %d\n", sizeof(unsigned short), len);
434                                 errorf(tmp);
435                         } else
436                                 ies->adsicpe = ntohs(*((unsigned short *)(data + 2)));
437                         break;
438                 case IAX_IE_DNID:
439                         ies->dnid = data + 2;
440                         break;
441                 case IAX_IE_RDNIS:
442                         ies->rdnis = data + 2;
443                         break;
444                 case IAX_IE_AUTHMETHODS:
445                         if (len != sizeof(unsigned short))  {
446                                 snprintf(tmp, sizeof(tmp), "Expecting authmethods to be %d bytes long but was %d\n", sizeof(unsigned short), len);
447                                 errorf(tmp);
448                         } else
449                                 ies->authmethods = ntohs(*((unsigned short *)(data + 2)));
450                         break;
451                 case IAX_IE_CHALLENGE:
452                         ies->challenge = data + 2;
453                         break;
454                 case IAX_IE_MD5_RESULT:
455                         ies->md5_result = data + 2;
456                         break;
457                 case IAX_IE_RSA_RESULT:
458                         ies->rsa_result = data + 2;
459                         break;
460                 case IAX_IE_APPARENT_ADDR:
461                         ies->apparent_addr = ((struct sockaddr_in *)(data + 2));
462                         break;
463                 case IAX_IE_REFRESH:
464                         if (len != sizeof(unsigned short)) {
465                                 snprintf(tmp, sizeof(tmp),  "Expecting refresh to be %d bytes long but was %d\n", sizeof(unsigned short), len);
466                                 errorf(tmp);
467                         } else
468                                 ies->refresh = ntohs(*((unsigned short *)(data + 2)));
469                         break;
470                 case IAX_IE_DPSTATUS:
471                         if (len != sizeof(unsigned short)) {
472                                 snprintf(tmp, sizeof(tmp),  "Expecting dpstatus to be %d bytes long but was %d\n", sizeof(unsigned short), len);
473                                 errorf(tmp);
474                         } else
475                                 ies->dpstatus = ntohs(*((unsigned short *)(data + 2)));
476                         break;
477                 case IAX_IE_CALLNO:
478                         if (len != sizeof(unsigned short)) {
479                                 snprintf(tmp, sizeof(tmp),  "Expecting callno to be %d bytes long but was %d\n", sizeof(unsigned short), len);
480                                 errorf(tmp);
481                         } else
482                                 ies->callno = ntohs(*((unsigned short *)(data + 2)));
483                         break;
484                 case IAX_IE_CAUSE:
485                         ies->cause = data + 2;
486                         break;
487                 case IAX_IE_IAX_UNKNOWN:
488                         if (len == 1)
489                                 ies->iax_unknown = data[2];
490                         else {
491                                 snprintf(tmp, sizeof(tmp), "Expected single byte Unknown command, but was %d long\n", len);
492                                 errorf(tmp);
493                         }
494                         break;
495                 case IAX_IE_MSGCOUNT:
496                         if (len != sizeof(unsigned short)) {
497                                 snprintf(tmp, sizeof(tmp), "Expecting msgcount to be %d bytes long but was %d\n", sizeof(unsigned short), len);
498                                 errorf(tmp);
499                         } else
500                                 ies->msgcount = ntohs(*((unsigned short *)(data + 2))); 
501                         break;
502                 case IAX_IE_AUTOANSWER:
503                         ies->autoanswer = 1;
504                         break;
505                 case IAX_IE_MUSICONHOLD:
506                         ies->musiconhold = 1;
507                         break;
508                 case IAX_IE_TRANSFERID:
509                         if (len != sizeof(unsigned int)) {
510                                 snprintf(tmp, sizeof(tmp), "Expecting transferid to be %d bytes long but was %d\n", sizeof(unsigned int), len);
511                                 errorf(tmp);
512                         } else
513                                 ies->transferid = ntohl(*((unsigned int *)(data + 2)));
514                         break;
515                 case IAX_IE_DATETIME:
516                         if (len != sizeof(unsigned int)) {
517                                 snprintf(tmp, sizeof(tmp), "Expecting date/time to be %d bytes long but was %d\n", sizeof(unsigned int), len);
518                                 errorf(tmp);
519                         } else
520                                 ies->datetime = ntohl(*((unsigned int *)(data + 2)));
521                         break;
522                 case IAX_IE_FIRMWAREVER:
523                         if (len != sizeof(unsigned short)) {
524                                 snprintf(tmp, sizeof(tmp), "Expecting firmwarever to be %d bytes long but was %d\n", sizeof(unsigned short), len);
525                                 errorf(tmp);
526                         } else
527                                 ies->firmwarever = ntohs(*((unsigned short *)(data + 2)));      
528                         break;
529                 case IAX_IE_DEVICETYPE:
530                         ies->devicetype = data + 2;
531                         break;
532                 case IAX_IE_SERVICEIDENT:
533                         ies->serviceident = data + 2;
534                         break;
535                 case IAX_IE_FWBLOCKDESC:
536                         if (len != sizeof(unsigned int)) {
537                                 snprintf(tmp, sizeof(tmp), "Expected block desc to be %d bytes long but was %d\n", sizeof(unsigned int), len);
538                                 errorf(tmp);
539                         } else
540                                 ies->fwdesc = ntohl(*((unsigned int *)(data + 2)));
541                         break;
542                 case IAX_IE_FWBLOCKDATA:
543                         ies->fwdata = data + 2;
544                         ies->fwdatalen = len;
545                         break;
546                 default:
547                         snprintf(tmp, sizeof(tmp), "Ignoring unknown information element '%s' (%d) of length %d\n", iax_ie2str(ie), ie, len);
548                         errorf(tmp);
549                 }
550                 /* Overwrite information element with 0, to null terminate previous portion */
551                 data[0] = 0;
552                 datalen -= (len + 2);
553                 data += (len + 2);
554         }
555         /* Null-terminate last field */
556         *data = '\0';
557         if (datalen) {
558                 errorf("Invalid information element contents, strange boundary\n");
559                 return -1;
560         }
561         return 0;
562 }
563
564 void iax_frame_wrap(struct iax_frame *fr, struct ast_frame *f)
565 {
566         fr->af.frametype = f->frametype;
567         fr->af.subclass = f->subclass;
568         fr->af.mallocd = 0;                             /* Our frame is static relative to the container */
569         fr->af.datalen = f->datalen;
570         fr->af.samples = f->samples;
571         fr->af.offset = AST_FRIENDLY_OFFSET;
572         fr->af.src = f->src;
573         fr->af.data = fr->afdata;
574         if (fr->af.datalen) 
575                 memcpy(fr->af.data, f->data, fr->af.datalen);
576 }
577
578 struct iax_frame *iax_frame_new(int direction, int datalen)
579 {
580         struct iax_frame *fr;
581         fr = malloc(sizeof(struct iax_frame) + datalen);
582         if (fr) {
583                 fr->direction = direction;
584                 fr->retrans = -1;
585                 frames++;
586                 if (fr->direction == DIRECTION_INGRESS)
587                         iframes++;
588                 else
589                         oframes++;
590         }
591         return fr;
592 }
593
594 void iax_frame_free(struct iax_frame *fr)
595 {
596         /* Note: does not remove from scheduler! */
597         if (fr->direction == DIRECTION_INGRESS)
598                 iframes--;
599         else if (fr->direction == DIRECTION_OUTGRESS)
600                 oframes--;
601         else {
602                 errorf("Attempt to double free frame detected\n");
603                 return;
604         }
605         fr->direction = 0;
606         free(fr);
607         frames--;
608 }
609
610 int iax_get_frames(void) { return frames; }
611 int iax_get_iframes(void) { return iframes; }
612 int iax_get_oframes(void) { return oframes; }