| /* $Id: isdn_divert.c,v 1.6.6.3 2001/09/23 22:24:36 kai Exp $ |
| * |
| * DSS1 main diversion supplementary handling for i4l. |
| * |
| * Copyright 1999 by Werner Cornelius (werner@isdn4linux.de) |
| * |
| * This software may be used and distributed according to the terms |
| * of the GNU General Public License, incorporated herein by reference. |
| * |
| */ |
| |
| #include <linux/proc_fs.h> |
| #include <linux/slab.h> |
| #include <linux/timer.h> |
| #include <linux/jiffies.h> |
| |
| #include "isdn_divert.h" |
| |
| /**********************************/ |
| /* structure keeping calling info */ |
| /**********************************/ |
| struct call_struc { |
| isdn_ctrl ics; /* delivered setup + driver parameters */ |
| ulong divert_id; /* Id delivered to user */ |
| unsigned char akt_state; /* actual state */ |
| char deflect_dest[35]; /* deflection destination */ |
| struct timer_list timer; /* timer control structure */ |
| char info[90]; /* device info output */ |
| struct call_struc *next; /* pointer to next entry */ |
| struct call_struc *prev; |
| }; |
| |
| |
| /********************************************/ |
| /* structure keeping deflection table entry */ |
| /********************************************/ |
| struct deflect_struc { |
| struct deflect_struc *next, *prev; |
| divert_rule rule; /* used rule */ |
| }; |
| |
| |
| /*****************************************/ |
| /* variables for main diversion services */ |
| /*****************************************/ |
| /* diversion/deflection processes */ |
| static struct call_struc *divert_head = NULL; /* head of remembered entrys */ |
| static ulong next_id = 1; /* next info id */ |
| static struct deflect_struc *table_head = NULL; |
| static struct deflect_struc *table_tail = NULL; |
| static unsigned char extern_wait_max = 4; /* maximum wait in s for external process */ |
| |
| DEFINE_SPINLOCK(divert_lock); |
| |
| /***************************/ |
| /* timer callback function */ |
| /***************************/ |
| static void deflect_timer_expire(ulong arg) |
| { |
| unsigned long flags; |
| struct call_struc *cs = (struct call_struc *) arg; |
| |
| spin_lock_irqsave(&divert_lock, flags); |
| del_timer(&cs->timer); /* delete active timer */ |
| spin_unlock_irqrestore(&divert_lock, flags); |
| |
| switch (cs->akt_state) { |
| case DEFLECT_PROCEED: |
| cs->ics.command = ISDN_CMD_HANGUP; /* cancel action */ |
| divert_if.ll_cmd(&cs->ics); |
| spin_lock_irqsave(&divert_lock, flags); |
| cs->akt_state = DEFLECT_AUTODEL; /* delete after timeout */ |
| cs->timer.expires = jiffies + (HZ * AUTODEL_TIME); |
| add_timer(&cs->timer); |
| spin_unlock_irqrestore(&divert_lock, flags); |
| break; |
| |
| case DEFLECT_ALERT: |
| cs->ics.command = ISDN_CMD_REDIR; /* protocol */ |
| strlcpy(cs->ics.parm.setup.phone, cs->deflect_dest, sizeof(cs->ics.parm.setup.phone)); |
| strcpy(cs->ics.parm.setup.eazmsn, "Testtext delayed"); |
| divert_if.ll_cmd(&cs->ics); |
| spin_lock_irqsave(&divert_lock, flags); |
| cs->akt_state = DEFLECT_AUTODEL; /* delete after timeout */ |
| cs->timer.expires = jiffies + (HZ * AUTODEL_TIME); |
| add_timer(&cs->timer); |
| spin_unlock_irqrestore(&divert_lock, flags); |
| break; |
| |
| case DEFLECT_AUTODEL: |
| default: |
| spin_lock_irqsave(&divert_lock, flags); |
| if (cs->prev) |
| cs->prev->next = cs->next; /* forward link */ |
| else |
| divert_head = cs->next; |
| if (cs->next) |
| cs->next->prev = cs->prev; /* back link */ |
| spin_unlock_irqrestore(&divert_lock, flags); |
| kfree(cs); |
| return; |
| |
| } /* switch */ |
| } /* deflect_timer_func */ |
| |
| |
| /*****************************************/ |
| /* handle call forwarding de/activations */ |
| /* 0 = deact, 1 = act, 2 = interrogate */ |
| /*****************************************/ |
| int cf_command(int drvid, int mode, |
| u_char proc, char *msn, |
| u_char service, char *fwd_nr, ulong *procid) |
| { |
| unsigned long flags; |
| int retval, msnlen; |
| int fwd_len; |
| char *p, *ielenp, tmp[60]; |
| struct call_struc *cs; |
| |
| if (strchr(msn, '.')) return (-EINVAL); /* subaddress not allowed in msn */ |
| if ((proc & 0x7F) > 2) return (-EINVAL); |
| proc &= 3; |
| p = tmp; |
| *p++ = 0x30; /* enumeration */ |
| ielenp = p++; /* remember total length position */ |
| *p++ = 0xa; /* proc tag */ |
| *p++ = 1; /* length */ |
| *p++ = proc & 0x7F; /* procedure to de/activate/interrogate */ |
| *p++ = 0xa; /* service tag */ |
| *p++ = 1; /* length */ |
| *p++ = service; /* service to handle */ |
| |
| if (mode == 1) { |
| if (!*fwd_nr) return (-EINVAL); /* destination missing */ |
| if (strchr(fwd_nr, '.')) return (-EINVAL); /* subaddress not allowed */ |
| fwd_len = strlen(fwd_nr); |
| *p++ = 0x30; /* number enumeration */ |
| *p++ = fwd_len + 2; /* complete forward to len */ |
| *p++ = 0x80; /* fwd to nr */ |
| *p++ = fwd_len; /* length of number */ |
| strcpy(p, fwd_nr); /* copy number */ |
| p += fwd_len; /* pointer beyond fwd */ |
| } /* activate */ |
| |
| msnlen = strlen(msn); |
| *p++ = 0x80; /* msn number */ |
| if (msnlen > 1) { |
| *p++ = msnlen; /* length */ |
| strcpy(p, msn); |
| p += msnlen; |
| } else |
| *p++ = 0; |
| |
| *ielenp = p - ielenp - 1; /* set total IE length */ |
| |
| /* allocate mem for information struct */ |
| if (!(cs = kmalloc(sizeof(struct call_struc), GFP_ATOMIC))) |
| return (-ENOMEM); /* no memory */ |
| setup_timer(&cs->timer, deflect_timer_expire, (ulong)cs); |
| cs->info[0] = '\0'; |
| cs->ics.driver = drvid; |
| cs->ics.command = ISDN_CMD_PROT_IO; /* protocol specific io */ |
| cs->ics.arg = DSS1_CMD_INVOKE; /* invoke supplementary service */ |
| cs->ics.parm.dss1_io.proc = (mode == 1) ? 7 : (mode == 2) ? 11 : 8; /* operation */ |
| cs->ics.parm.dss1_io.timeout = 4000; /* from ETS 300 207-1 */ |
| cs->ics.parm.dss1_io.datalen = p - tmp; /* total len */ |
| cs->ics.parm.dss1_io.data = tmp; /* start of buffer */ |
| |
| spin_lock_irqsave(&divert_lock, flags); |
| cs->ics.parm.dss1_io.ll_id = next_id++; /* id for callback */ |
| spin_unlock_irqrestore(&divert_lock, flags); |
| *procid = cs->ics.parm.dss1_io.ll_id; |
| |
| sprintf(cs->info, "%d 0x%lx %s%s 0 %s %02x %d%s%s\n", |
| (!mode) ? DIVERT_DEACTIVATE : (mode == 1) ? DIVERT_ACTIVATE : DIVERT_REPORT, |
| cs->ics.parm.dss1_io.ll_id, |
| (mode != 2) ? "" : "0 ", |
| divert_if.drv_to_name(cs->ics.driver), |
| msn, |
| service & 0xFF, |
| proc, |
| (mode != 1) ? "" : " 0 ", |
| (mode != 1) ? "" : fwd_nr); |
| |
| retval = divert_if.ll_cmd(&cs->ics); /* execute command */ |
| |
| if (!retval) { |
| cs->prev = NULL; |
| spin_lock_irqsave(&divert_lock, flags); |
| cs->next = divert_head; |
| divert_head = cs; |
| spin_unlock_irqrestore(&divert_lock, flags); |
| } else |
| kfree(cs); |
| return (retval); |
| } /* cf_command */ |
| |
| |
| /****************************************/ |
| /* handle a external deflection command */ |
| /****************************************/ |
| int deflect_extern_action(u_char cmd, ulong callid, char *to_nr) |
| { |
| struct call_struc *cs; |
| isdn_ctrl ic; |
| unsigned long flags; |
| int i; |
| |
| if ((cmd & 0x7F) > 2) return (-EINVAL); /* invalid command */ |
| cs = divert_head; /* start of parameter list */ |
| while (cs) { |
| if (cs->divert_id == callid) break; /* found */ |
| cs = cs->next; |
| } /* search entry */ |
| if (!cs) return (-EINVAL); /* invalid callid */ |
| |
| ic.driver = cs->ics.driver; |
| ic.arg = cs->ics.arg; |
| i = -EINVAL; |
| if (cs->akt_state == DEFLECT_AUTODEL) return (i); /* no valid call */ |
| switch (cmd & 0x7F) { |
| case 0: /* hangup */ |
| del_timer(&cs->timer); |
| ic.command = ISDN_CMD_HANGUP; |
| i = divert_if.ll_cmd(&ic); |
| spin_lock_irqsave(&divert_lock, flags); |
| cs->akt_state = DEFLECT_AUTODEL; /* delete after timeout */ |
| cs->timer.expires = jiffies + (HZ * AUTODEL_TIME); |
| add_timer(&cs->timer); |
| spin_unlock_irqrestore(&divert_lock, flags); |
| break; |
| |
| case 1: /* alert */ |
| if (cs->akt_state == DEFLECT_ALERT) return (0); |
| cmd &= 0x7F; /* never wait */ |
| del_timer(&cs->timer); |
| ic.command = ISDN_CMD_ALERT; |
| if ((i = divert_if.ll_cmd(&ic))) { |
| spin_lock_irqsave(&divert_lock, flags); |
| cs->akt_state = DEFLECT_AUTODEL; /* delete after timeout */ |
| cs->timer.expires = jiffies + (HZ * AUTODEL_TIME); |
| add_timer(&cs->timer); |
| spin_unlock_irqrestore(&divert_lock, flags); |
| } else |
| cs->akt_state = DEFLECT_ALERT; |
| break; |
| |
| case 2: /* redir */ |
| del_timer(&cs->timer); |
| strlcpy(cs->ics.parm.setup.phone, to_nr, sizeof(cs->ics.parm.setup.phone)); |
| strcpy(cs->ics.parm.setup.eazmsn, "Testtext manual"); |
| ic.command = ISDN_CMD_REDIR; |
| if ((i = divert_if.ll_cmd(&ic))) { |
| spin_lock_irqsave(&divert_lock, flags); |
| cs->akt_state = DEFLECT_AUTODEL; /* delete after timeout */ |
| cs->timer.expires = jiffies + (HZ * AUTODEL_TIME); |
| add_timer(&cs->timer); |
| spin_unlock_irqrestore(&divert_lock, flags); |
| } else |
| cs->akt_state = DEFLECT_ALERT; |
| break; |
| |
| } /* switch */ |
| return (i); |
| } /* deflect_extern_action */ |
| |
| /********************************/ |
| /* insert a new rule before idx */ |
| /********************************/ |
| int insertrule(int idx, divert_rule *newrule) |
| { |
| struct deflect_struc *ds, *ds1 = NULL; |
| unsigned long flags; |
| |
| if (!(ds = kmalloc(sizeof(struct deflect_struc), GFP_KERNEL))) |
| return (-ENOMEM); /* no memory */ |
| |
| ds->rule = *newrule; /* set rule */ |
| |
| spin_lock_irqsave(&divert_lock, flags); |
| |
| if (idx >= 0) { |
| ds1 = table_head; |
| while ((ds1) && (idx > 0)) |
| { idx--; |
| ds1 = ds1->next; |
| } |
| if (!ds1) idx = -1; |
| } |
| |
| if (idx < 0) { |
| ds->prev = table_tail; /* previous entry */ |
| ds->next = NULL; /* end of chain */ |
| if (ds->prev) |
| ds->prev->next = ds; /* last forward */ |
| else |
| table_head = ds; /* is first entry */ |
| table_tail = ds; /* end of queue */ |
| } else { |
| ds->next = ds1; /* next entry */ |
| ds->prev = ds1->prev; /* prev entry */ |
| ds1->prev = ds; /* backward chain old element */ |
| if (!ds->prev) |
| table_head = ds; /* first element */ |
| } |
| |
| spin_unlock_irqrestore(&divert_lock, flags); |
| return (0); |
| } /* insertrule */ |
| |
| /***********************************/ |
| /* delete the rule at position idx */ |
| /***********************************/ |
| int deleterule(int idx) |
| { |
| struct deflect_struc *ds, *ds1; |
| unsigned long flags; |
| |
| if (idx < 0) { |
| spin_lock_irqsave(&divert_lock, flags); |
| ds = table_head; |
| table_head = NULL; |
| table_tail = NULL; |
| spin_unlock_irqrestore(&divert_lock, flags); |
| while (ds) { |
| ds1 = ds; |
| ds = ds->next; |
| kfree(ds1); |
| } |
| return (0); |
| } |
| |
| spin_lock_irqsave(&divert_lock, flags); |
| ds = table_head; |
| |
| while ((ds) && (idx > 0)) { |
| idx--; |
| ds = ds->next; |
| } |
| |
| if (!ds) { |
| spin_unlock_irqrestore(&divert_lock, flags); |
| return (-EINVAL); |
| } |
| |
| if (ds->next) |
| ds->next->prev = ds->prev; /* backward chain */ |
| else |
| table_tail = ds->prev; /* end of chain */ |
| |
| if (ds->prev) |
| ds->prev->next = ds->next; /* forward chain */ |
| else |
| table_head = ds->next; /* start of chain */ |
| |
| spin_unlock_irqrestore(&divert_lock, flags); |
| kfree(ds); |
| return (0); |
| } /* deleterule */ |
| |
| /*******************************************/ |
| /* get a pointer to a specific rule number */ |
| /*******************************************/ |
| divert_rule *getruleptr(int idx) |
| { |
| struct deflect_struc *ds = table_head; |
| |
| if (idx < 0) return (NULL); |
| while ((ds) && (idx >= 0)) { |
| if (!(idx--)) { |
| return (&ds->rule); |
| break; |
| } |
| ds = ds->next; |
| } |
| return (NULL); |
| } /* getruleptr */ |
| |
| /*************************************************/ |
| /* called from common module on an incoming call */ |
| /*************************************************/ |
| static int isdn_divert_icall(isdn_ctrl *ic) |
| { |
| int retval = 0; |
| unsigned long flags; |
| struct call_struc *cs = NULL; |
| struct deflect_struc *dv; |
| char *p, *p1; |
| u_char accept; |
| |
| /* first check the internal deflection table */ |
| for (dv = table_head; dv; dv = dv->next) { |
| /* scan table */ |
| if (((dv->rule.callopt == 1) && (ic->command == ISDN_STAT_ICALLW)) || |
| ((dv->rule.callopt == 2) && (ic->command == ISDN_STAT_ICALL))) |
| continue; /* call option check */ |
| if (!(dv->rule.drvid & (1L << ic->driver))) |
| continue; /* driver not matching */ |
| if ((dv->rule.si1) && (dv->rule.si1 != ic->parm.setup.si1)) |
| continue; /* si1 not matching */ |
| if ((dv->rule.si2) && (dv->rule.si2 != ic->parm.setup.si2)) |
| continue; /* si2 not matching */ |
| |
| p = dv->rule.my_msn; |
| p1 = ic->parm.setup.eazmsn; |
| accept = 0; |
| while (*p) { |
| /* complete compare */ |
| if (*p == '-') { |
| accept = 1; /* call accepted */ |
| break; |
| } |
| if (*p++ != *p1++) |
| break; /* not accepted */ |
| if ((!*p) && (!*p1)) |
| accept = 1; |
| } /* complete compare */ |
| if (!accept) continue; /* not accepted */ |
| |
| if ((strcmp(dv->rule.caller, "0")) || |
| (ic->parm.setup.phone[0])) { |
| p = dv->rule.caller; |
| p1 = ic->parm.setup.phone; |
| accept = 0; |
| while (*p) { |
| /* complete compare */ |
| if (*p == '-') { |
| accept = 1; /* call accepted */ |
| break; |
| } |
| if (*p++ != *p1++) |
| break; /* not accepted */ |
| if ((!*p) && (!*p1)) |
| accept = 1; |
| } /* complete compare */ |
| if (!accept) continue; /* not accepted */ |
| } |
| |
| switch (dv->rule.action) { |
| case DEFLECT_IGNORE: |
| return 0; |
| |
| case DEFLECT_ALERT: |
| case DEFLECT_PROCEED: |
| case DEFLECT_REPORT: |
| case DEFLECT_REJECT: |
| if (dv->rule.action == DEFLECT_PROCEED) |
| if ((!if_used) || ((!extern_wait_max) && (!dv->rule.waittime))) |
| return (0); /* no external deflection needed */ |
| if (!(cs = kmalloc(sizeof(struct call_struc), GFP_ATOMIC))) |
| return (0); /* no memory */ |
| setup_timer(&cs->timer, deflect_timer_expire, |
| (ulong)cs); |
| cs->info[0] = '\0'; |
| |
| cs->ics = *ic; /* copy incoming data */ |
| if (!cs->ics.parm.setup.phone[0]) strcpy(cs->ics.parm.setup.phone, "0"); |
| if (!cs->ics.parm.setup.eazmsn[0]) strcpy(cs->ics.parm.setup.eazmsn, "0"); |
| cs->ics.parm.setup.screen = dv->rule.screen; |
| if (dv->rule.waittime) |
| cs->timer.expires = jiffies + (HZ * dv->rule.waittime); |
| else if (dv->rule.action == DEFLECT_PROCEED) |
| cs->timer.expires = jiffies + (HZ * extern_wait_max); |
| else |
| cs->timer.expires = 0; |
| cs->akt_state = dv->rule.action; |
| spin_lock_irqsave(&divert_lock, flags); |
| cs->divert_id = next_id++; /* new sequence number */ |
| spin_unlock_irqrestore(&divert_lock, flags); |
| cs->prev = NULL; |
| if (cs->akt_state == DEFLECT_ALERT) { |
| strcpy(cs->deflect_dest, dv->rule.to_nr); |
| if (!cs->timer.expires) { |
| strcpy(ic->parm.setup.eazmsn, |
| "Testtext direct"); |
| ic->parm.setup.screen = dv->rule.screen; |
| strlcpy(ic->parm.setup.phone, dv->rule.to_nr, sizeof(ic->parm.setup.phone)); |
| cs->akt_state = DEFLECT_AUTODEL; /* delete after timeout */ |
| cs->timer.expires = jiffies + (HZ * AUTODEL_TIME); |
| retval = 5; |
| } else |
| retval = 1; /* alerting */ |
| } else { |
| cs->deflect_dest[0] = '\0'; |
| retval = 4; /* only proceed */ |
| } |
| sprintf(cs->info, "%d 0x%lx %s %s %s %s 0x%x 0x%x %d %d %s\n", |
| cs->akt_state, |
| cs->divert_id, |
| divert_if.drv_to_name(cs->ics.driver), |
| (ic->command == ISDN_STAT_ICALLW) ? "1" : "0", |
| cs->ics.parm.setup.phone, |
| cs->ics.parm.setup.eazmsn, |
| cs->ics.parm.setup.si1, |
| cs->ics.parm.setup.si2, |
| cs->ics.parm.setup.screen, |
| dv->rule.waittime, |
| cs->deflect_dest); |
| if ((dv->rule.action == DEFLECT_REPORT) || |
| (dv->rule.action == DEFLECT_REJECT)) { |
| put_info_buffer(cs->info); |
| kfree(cs); /* remove */ |
| return ((dv->rule.action == DEFLECT_REPORT) ? 0 : 2); /* nothing to do */ |
| } |
| break; |
| |
| default: |
| return 0; /* ignore call */ |
| } /* switch action */ |
| break; /* will break the 'for' looping */ |
| } /* scan_table */ |
| |
| if (cs) { |
| cs->prev = NULL; |
| spin_lock_irqsave(&divert_lock, flags); |
| cs->next = divert_head; |
| divert_head = cs; |
| if (cs->timer.expires) add_timer(&cs->timer); |
| spin_unlock_irqrestore(&divert_lock, flags); |
| |
| put_info_buffer(cs->info); |
| return (retval); |
| } else |
| return (0); |
| } /* isdn_divert_icall */ |
| |
| |
| void deleteprocs(void) |
| { |
| struct call_struc *cs, *cs1; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&divert_lock, flags); |
| cs = divert_head; |
| divert_head = NULL; |
| while (cs) { |
| del_timer(&cs->timer); |
| cs1 = cs; |
| cs = cs->next; |
| kfree(cs1); |
| } |
| spin_unlock_irqrestore(&divert_lock, flags); |
| } /* deleteprocs */ |
| |
| /****************************************************/ |
| /* put a address including address type into buffer */ |
| /****************************************************/ |
| static int put_address(char *st, u_char *p, int len) |
| { |
| u_char retval = 0; |
| u_char adr_typ = 0; /* network standard */ |
| |
| if (len < 2) return (retval); |
| if (*p == 0xA1) { |
| retval = *(++p) + 2; /* total length */ |
| if (retval > len) return (0); /* too short */ |
| len = retval - 2; /* remaining length */ |
| if (len < 3) return (0); |
| if ((*(++p) != 0x0A) || (*(++p) != 1)) return (0); |
| adr_typ = *(++p); |
| len -= 3; |
| p++; |
| if (len < 2) return (0); |
| if (*p++ != 0x12) return (0); |
| if (*p > len) return (0); /* check number length */ |
| len = *p++; |
| } else if (*p == 0x80) { |
| retval = *(++p) + 2; /* total length */ |
| if (retval > len) return (0); |
| len = retval - 2; |
| p++; |
| } else |
| return (0); /* invalid address information */ |
| |
| sprintf(st, "%d ", adr_typ); |
| st += strlen(st); |
| if (!len) |
| *st++ = '-'; |
| else |
| while (len--) |
| *st++ = *p++; |
| *st = '\0'; |
| return (retval); |
| } /* put_address */ |
| |
| /*************************************/ |
| /* report a successful interrogation */ |
| /*************************************/ |
| static int interrogate_success(isdn_ctrl *ic, struct call_struc *cs) |
| { |
| char *src = ic->parm.dss1_io.data; |
| int restlen = ic->parm.dss1_io.datalen; |
| int cnt = 1; |
| u_char n, n1; |
| char st[90], *p, *stp; |
| |
| if (restlen < 2) return (-100); /* frame too short */ |
| if (*src++ != 0x30) return (-101); |
| if ((n = *src++) > 0x81) return (-102); /* invalid length field */ |
| restlen -= 2; /* remaining bytes */ |
| if (n == 0x80) { |
| if (restlen < 2) return (-103); |
| if ((*(src + restlen - 1)) || (*(src + restlen - 2))) return (-104); |
| restlen -= 2; |
| } else if (n == 0x81) { |
| n = *src++; |
| restlen--; |
| if (n > restlen) return (-105); |
| restlen = n; |
| } else if (n > restlen) |
| return (-106); |
| else |
| restlen = n; /* standard format */ |
| if (restlen < 3) return (-107); /* no procedure */ |
| if ((*src++ != 2) || (*src++ != 1) || (*src++ != 0x0B)) return (-108); |
| restlen -= 3; |
| if (restlen < 2) return (-109); /* list missing */ |
| if (*src == 0x31) { |
| src++; |
| if ((n = *src++) > 0x81) return (-110); /* invalid length field */ |
| restlen -= 2; /* remaining bytes */ |
| if (n == 0x80) { |
| if (restlen < 2) return (-111); |
| if ((*(src + restlen - 1)) || (*(src + restlen - 2))) return (-112); |
| restlen -= 2; |
| } else if (n == 0x81) { |
| n = *src++; |
| restlen--; |
| if (n > restlen) return (-113); |
| restlen = n; |
| } else if (n > restlen) |
| return (-114); |
| else |
| restlen = n; /* standard format */ |
| } /* result list header */ |
| |
| while (restlen >= 2) { |
| stp = st; |
| sprintf(stp, "%d 0x%lx %d %s ", DIVERT_REPORT, ic->parm.dss1_io.ll_id, |
| cnt++, divert_if.drv_to_name(ic->driver)); |
| stp += strlen(stp); |
| if (*src++ != 0x30) return (-115); /* invalid enum */ |
| n = *src++; |
| restlen -= 2; |
| if (n > restlen) return (-116); /* enum length wrong */ |
| restlen -= n; |
| p = src; /* one entry */ |
| src += n; |
| if (!(n1 = put_address(stp, p, n & 0xFF))) continue; |
| stp += strlen(stp); |
| p += n1; |
| n -= n1; |
| if (n < 6) continue; /* no service and proc */ |
| if ((*p++ != 0x0A) || (*p++ != 1)) continue; |
| sprintf(stp, " 0x%02x ", (*p++) & 0xFF); |
| stp += strlen(stp); |
| if ((*p++ != 0x0A) || (*p++ != 1)) continue; |
| sprintf(stp, "%d ", (*p++) & 0xFF); |
| stp += strlen(stp); |
| n -= 6; |
| if (n > 2) { |
| if (*p++ != 0x30) continue; |
| if (*p > (n - 2)) continue; |
| n = *p++; |
| if (!(n1 = put_address(stp, p, n & 0xFF))) continue; |
| stp += strlen(stp); |
| } |
| sprintf(stp, "\n"); |
| put_info_buffer(st); |
| } /* while restlen */ |
| if (restlen) return (-117); |
| return (0); |
| } /* interrogate_success */ |
| |
| /*********************************************/ |
| /* callback for protocol specific extensions */ |
| /*********************************************/ |
| static int prot_stat_callback(isdn_ctrl *ic) |
| { |
| struct call_struc *cs, *cs1; |
| int i; |
| unsigned long flags; |
| |
| cs = divert_head; /* start of list */ |
| cs1 = NULL; |
| while (cs) { |
| if (ic->driver == cs->ics.driver) { |
| switch (cs->ics.arg) { |
| case DSS1_CMD_INVOKE: |
| if ((cs->ics.parm.dss1_io.ll_id == ic->parm.dss1_io.ll_id) && |
| (cs->ics.parm.dss1_io.hl_id == ic->parm.dss1_io.hl_id)) { |
| switch (ic->arg) { |
| case DSS1_STAT_INVOKE_ERR: |
| sprintf(cs->info, "128 0x%lx 0x%x\n", |
| ic->parm.dss1_io.ll_id, |
| ic->parm.dss1_io.timeout); |
| put_info_buffer(cs->info); |
| break; |
| |
| case DSS1_STAT_INVOKE_RES: |
| switch (cs->ics.parm.dss1_io.proc) { |
| case 7: |
| case 8: |
| put_info_buffer(cs->info); |
| break; |
| |
| case 11: |
| i = interrogate_success(ic, cs); |
| if (i) |
| sprintf(cs->info, "%d 0x%lx %d\n", DIVERT_REPORT, |
| ic->parm.dss1_io.ll_id, i); |
| put_info_buffer(cs->info); |
| break; |
| |
| default: |
| printk(KERN_WARNING "dss1_divert: unknown proc %d\n", cs->ics.parm.dss1_io.proc); |
| break; |
| } |
| |
| break; |
| |
| default: |
| printk(KERN_WARNING "dss1_divert unknown invoke answer %lx\n", ic->arg); |
| break; |
| } |
| cs1 = cs; /* remember structure */ |
| cs = NULL; |
| continue; /* abort search */ |
| } /* id found */ |
| break; |
| |
| case DSS1_CMD_INVOKE_ABORT: |
| printk(KERN_WARNING "dss1_divert unhandled invoke abort\n"); |
| break; |
| |
| default: |
| printk(KERN_WARNING "dss1_divert unknown cmd 0x%lx\n", cs->ics.arg); |
| break; |
| } /* switch ics.arg */ |
| cs = cs->next; |
| } /* driver ok */ |
| } |
| |
| if (!cs1) { |
| printk(KERN_WARNING "dss1_divert unhandled process\n"); |
| return (0); |
| } |
| |
| if (cs1->ics.driver == -1) { |
| spin_lock_irqsave(&divert_lock, flags); |
| del_timer(&cs1->timer); |
| if (cs1->prev) |
| cs1->prev->next = cs1->next; /* forward link */ |
| else |
| divert_head = cs1->next; |
| if (cs1->next) |
| cs1->next->prev = cs1->prev; /* back link */ |
| spin_unlock_irqrestore(&divert_lock, flags); |
| kfree(cs1); |
| } |
| |
| return (0); |
| } /* prot_stat_callback */ |
| |
| |
| /***************************/ |
| /* status callback from HL */ |
| /***************************/ |
| static int isdn_divert_stat_callback(isdn_ctrl *ic) |
| { |
| struct call_struc *cs, *cs1; |
| unsigned long flags; |
| int retval; |
| |
| retval = -1; |
| cs = divert_head; /* start of list */ |
| while (cs) { |
| if ((ic->driver == cs->ics.driver) && |
| (ic->arg == cs->ics.arg)) { |
| switch (ic->command) { |
| case ISDN_STAT_DHUP: |
| sprintf(cs->info, "129 0x%lx\n", cs->divert_id); |
| del_timer(&cs->timer); |
| cs->ics.driver = -1; |
| break; |
| |
| case ISDN_STAT_CAUSE: |
| sprintf(cs->info, "130 0x%lx %s\n", cs->divert_id, ic->parm.num); |
| break; |
| |
| case ISDN_STAT_REDIR: |
| sprintf(cs->info, "131 0x%lx\n", cs->divert_id); |
| del_timer(&cs->timer); |
| cs->ics.driver = -1; |
| break; |
| |
| default: |
| sprintf(cs->info, "999 0x%lx 0x%x\n", cs->divert_id, (int)(ic->command)); |
| break; |
| } |
| put_info_buffer(cs->info); |
| retval = 0; |
| } |
| cs1 = cs; |
| cs = cs->next; |
| if (cs1->ics.driver == -1) { |
| spin_lock_irqsave(&divert_lock, flags); |
| if (cs1->prev) |
| cs1->prev->next = cs1->next; /* forward link */ |
| else |
| divert_head = cs1->next; |
| if (cs1->next) |
| cs1->next->prev = cs1->prev; /* back link */ |
| spin_unlock_irqrestore(&divert_lock, flags); |
| kfree(cs1); |
| } |
| } |
| return (retval); /* not found */ |
| } /* isdn_divert_stat_callback */ |
| |
| |
| /********************/ |
| /* callback from ll */ |
| /********************/ |
| int ll_callback(isdn_ctrl *ic) |
| { |
| switch (ic->command) { |
| case ISDN_STAT_ICALL: |
| case ISDN_STAT_ICALLW: |
| return (isdn_divert_icall(ic)); |
| break; |
| |
| case ISDN_STAT_PROT: |
| if ((ic->arg & 0xFF) == ISDN_PTYPE_EURO) { |
| if (ic->arg != DSS1_STAT_INVOKE_BRD) |
| return (prot_stat_callback(ic)); |
| else |
| return (0); /* DSS1 invoke broadcast */ |
| } else |
| return (-1); /* protocol not euro */ |
| |
| default: |
| return (isdn_divert_stat_callback(ic)); |
| } |
| } /* ll_callback */ |