/* * CAPI interface handling * * heavily based on capiplugin.c of pppdcapiplugin by * Carsten Paeth (calle@calle.in-berlin.de) * * Copyright 2004 SYSGO Real-Time Solutions AG * Klein-Winternheim, Germany * All rights reserved. * * Author: Armin Schindler * * $Id$ * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. See . * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ #include #include #include #include #include #include #include #include #include #include "isdntun.h" #include "capiconn.h" #include "options.h" #include #include #include /* -------------------------------------------------------------------- */ static int exiting = 0; /* -------------------------------------------------------------------- */ typedef struct stringlist { struct stringlist *next; char *s; } STRINGLIST; /* -------------------------------------------------------------------- */ static int curphase = -1; static int wakeupnow = 0; /* -------------------------------------------------------------------- */ static void handlemessages(void) ; static int shmatch(char *string, char *expr); static void stringlist_free(STRINGLIST **pp); static int stringlist_append_string(STRINGLIST **pp, char *s); static STRINGLIST *stringlist_split(char *tosplit, char *seps); static void makecallback(void); static char *phase2str(int phase); static void wakeupdemand(void); /* -------------------------------------------------------------------- */ static capiconn_context *ctx; static unsigned applid; #define CM(x) (1<<(x)) #define CIPMASK_ALL 0x1FFF03FF #define CIPMASK_VOICE (CM(1)|CM(4)|CM(5)|CM(16)|CM(26)) #define CIPMASK_DATA (CM(2)|CM(3)) static unsigned long cipmask = CIPMASK_ALL; static int controller = 1; static capi_contrinfo cinfo = { 0 , 0, 0 }; /* -------------------------------------------------------------------- */ /* * numbers */ static STRINGLIST *numbers; static STRINGLIST *callbacknumbers; static STRINGLIST *clis; static STRINGLIST *inmsns; static STRINGLIST *controllers; /* * protocol */ #define PROTO_HDLC 0 #define PROTO_X75 1 static int proto = PROTO_HDLC; /* * leased line */ static unsigned char AdditionalInfo[1+2+2+31]; /* * callback & COSO */ #define COSO_CALLER 0 #define COSO_LOCAL 1 #define COSO_REMOTE 2 static int coso = COSO_CALLER; /* -------------------------------------------------------------------- */ /* -------- Handle leased lines (CAPI-Bundling) ----------------------- */ /* -------------------------------------------------------------------- */ static int decodechannels(char *teln, unsigned long *bmaskp, int *activep) { unsigned long bmask = 0; int active = !0; char *s; int channel; int i; s = teln; while (*s && *s == ' ') s++; if (!*s) fatal("option channels: list empty"); if (*s == 'p' || *s == 'P') { active = 0; s++; } if (*s == 'a' || *s == 'A') { active = !0; s++; } while (*s) { int digit1 = 0; int digit2 = 0; if (!isdigit(*s)) goto illegal; while (isdigit(*s)) { digit1 = digit1*10 + (*s - '0'); s++; } channel = digit1; if (channel <= 0 && channel > 30) goto rangeerror; if (*s == 0 || *s == ',' || *s == ' ') { bmask |= (1 << digit1); digit1 = 0; if (*s) s++; continue; } if (*s != '-') goto illegal; s++; if (!isdigit(*s)) return -3; while (isdigit(*s)) { digit2 = digit2*10 + (*s - '0'); s++; } channel = digit2; if (channel <= 0 && channel > 30) goto rangeerror; if (*s == 0 || *s == ',' || *s == ' ') { if (digit1 > digit2) for (i = digit2; i <= digit1 ; i++) bmask |= (1 << i); else for (i = digit1; i <= digit2 ; i++) bmask |= (1 << i); digit1 = digit2 = 0; if (*s) s++; continue; } goto illegal; } if (activep) *activep = active; if (bmaskp) *bmaskp = bmask; return 0; illegal: fatal("option channels: illegal octet '%c'", *s); return -1; rangeerror: fatal("option channels: channel %d out of range", channel); return -1; } static int channels2capi20(char *teln, unsigned char *AdditionalInfo) { unsigned long bmask; int active; int i; decodechannels(teln, &bmask, &active); /* info("\"%s\" 0x%lx %d\n", teln, bmask, active); */ /* Length */ AdditionalInfo[0] = 2+2+31; /* Channel: 3 => use channel allocation */ AdditionalInfo[1] = 3; AdditionalInfo[2] = 0; /* Operation: 0 => DTE mode, 1 => DCE mode */ if (active) { AdditionalInfo[3] = 0; AdditionalInfo[4] = 0; } else { AdditionalInfo[3] = 1; AdditionalInfo[4] = 0; } /* Channel mask array */ AdditionalInfo[5] = 0; /* no D-Channel */ for (i=1; i <= 30; i++) AdditionalInfo[5+i] = (bmask & (1 << i)) ? 0xff : 0; return 0; } /* -------------------------------------------------------------------- */ static void itun_check_options(void) { static int init = 0; if (init) return; init = 1; /* * protocol */ if (strcasecmp(opt_proto, "hdlc") == 0) { proto = PROTO_HDLC; } else if (strcasecmp(opt_proto, "x75") == 0) { proto = PROTO_X75; } else { option_error("unknown protocol \"%s\"", opt_proto); die(1); } cipmask = CIPMASK_DATA; /* * coso */ if (opt_coso == 0) { if (opt_cbflag) { if (opt_number) coso = COSO_REMOTE; else coso = COSO_LOCAL; } else { coso = COSO_CALLER; } } else if (strcasecmp(opt_coso, "caller") == 0) { coso = COSO_CALLER; } else if (strcasecmp(opt_coso, "local") == 0) { coso = COSO_LOCAL; } else if (strcasecmp(opt_coso, "remote") == 0) { coso = COSO_REMOTE; } else { option_error("wrong value for option coso"); die(1); } if (opt_cbflag) { if (opt_coso) { option_error("option clicb ignored"); } else if (coso == COSO_REMOTE) { dbglog("clicb selected coso remote"); } else if (coso == COSO_LOCAL) { dbglog("clicb selected coso local"); } else { option_error("option clicb ignored"); } } /* * leased line */ if (opt_channels) { channels2capi20(opt_channels, AdditionalInfo); if (opt_number) option_error("option number ignored"); if (opt_numberprefix) option_error("option numberprefix ignored"); if (opt_callbacknumber) option_error("option callbacknumber ignored"); if (opt_msn) option_error("option msn ignored"); if (opt_inmsn) option_error("option inmsn ignored"); /* * dialout */ } else if (opt_number) { stringlist_free(&numbers); numbers = stringlist_split(opt_number, " \t,"); /* * dialin */ } else { if (coso == COSO_LOCAL) { if (opt_callbacknumber == 0) { option_error("option callbacknumber missing"); die(1); } } } if (opt_callbacknumber) { stringlist_free(&callbacknumbers); callbacknumbers = stringlist_split(opt_callbacknumber, " \t,"); } /* * ddi */ memset(&cinfo, 0, sizeof(cinfo)); if (opt_ddi) { STRINGLIST *parsed_ddi; STRINGLIST *sl; char *tmp; parsed_ddi = stringlist_split(opt_ddi, " \t,"); sl = parsed_ddi; cinfo.ddi = strdup(sl->s); if (cinfo.ddi == 0) { stringlist_free(&parsed_ddi); option_error("option ddi error"); die(1); } if (sl->next && sl->next->s) { sl = sl->next; cinfo.ndigits = strtol(sl->s, &tmp, 10); if (tmp == sl->s || *tmp) { stringlist_free(&parsed_ddi); option_error("option ddi error"); die(1); } } stringlist_free(&parsed_ddi); } /* * controller */ if (opt_controller) { STRINGLIST *sl; int count = 0, contr; stringlist_free(&controllers); controllers = stringlist_split(opt_controller, " \t,"); for (sl = controllers; sl; sl = sl->next) { count++; contr = strtol(sl->s, NULL, 10); if ((contr < 1) || (contr > 30)) { option_error("illegal controller specification \"%s\"", opt_controller); die(1); } if (count == 1) controller = contr; } if (count < 2) { stringlist_free(&controllers); } } /* * cli & inmsn */ if (opt_cli) { STRINGLIST *sl; char *old; stringlist_free(&clis); clis = stringlist_split(opt_cli, " \t,"); for (sl = clis; sl; sl = sl->next) { if (*sl->s != '*') { old = sl->s; sl->s = (char *)malloc(strlen(old)+2); if (sl->s) { *sl->s = '*'; strcpy(sl->s+1, old); free(old); } else { sl->s = old; option_error("prepend '*' to cli failed"); } } } } if (opt_inmsn) { stringlist_free(&inmsns); inmsns = stringlist_split(opt_inmsn, " \t,"); } /* * dial on demand */ if (demand) { if (!opt_number && !opt_channels) { option_error("number or channels missing for demand"); die(1); } if (opt_voicecallwakeup) cipmask |= CIPMASK_VOICE; } else if (opt_voicecallwakeup) { option_error("option voicecallwakeup ignored"); opt_voicecallwakeup = 0; } return; } /* -------------------------------------------------------------------- */ /* -------- Match with * and ? ---------------------------------------- */ /* -------------------------------------------------------------------- */ static int shmatch(char *string, char *expr) { char *match = expr; char *s = string; char *p; int escape = 0; while (*match && *s) { if (escape) { if (*s != *match) return 0; s++; match++; } else if (*match == '\\') { match++; escape = 1; } else if (*match == '*') { match++; if (*match == 0) return 1; if (*match == '\\') match++; while ((p = strchr(s, *match)) != 0) { if (shmatch(p+1, match+1)) return 1; s = p + 1; } return 0; } else if (*match == '?') { s++; match++; } else { if (*s != *match) return 0; s++; match++; } } if (*s == 0) { if (*match == 0) return 1; if (*match == '*' && match[1] == 0) return 1; } return 0; } /* -------------------------------------------------------------------- */ /* -------- STRINGLIST for parsing ------------------------------------ */ /* -------------------------------------------------------------------- */ static void stringlist_free(STRINGLIST **pp) { STRINGLIST *p, *next; p = *pp; while (p) { next = p->next; if (p->s) free(p->s); free(p); p = next; } *pp = 0; } static int stringlist_append_string(STRINGLIST **pp, char *s) { STRINGLIST *p; for (; *pp; pp = &(*pp)->next) ; if ((p = (STRINGLIST *)malloc(sizeof(STRINGLIST))) == 0) return -1; memset(p, 0, sizeof(STRINGLIST)); if ((p->s = strdup(s)) == 0) { free(p); return -1; } p->next = 0; *pp = p; return 0; } static STRINGLIST *stringlist_split(char *tosplit, char *seps) { STRINGLIST *p = 0; char *str = strdup(tosplit); char *s; if (!str) return 0; for (s = strtok(str, seps); s; s = strtok(0, seps)) { if (*s == 0) continue; /* if strtok is not working correkt */ if (stringlist_append_string(&p, s) < 0) { stringlist_free(&p); free(str); return 0; } } free(str); return p; } /* -------------------------------------------------------------------- */ /* -------- connection management ------------------------------------- */ /* -------------------------------------------------------------------- */ typedef struct conn { struct conn *next; capi_connection *conn; int type; #define CONNTYPE_OUTGOING 0 #define CONNTYPE_INCOMING 1 #define CONNTYPE_IGNORE 2 #define CONNTYPE_REJECT 3 #define CONNTYPE_FOR_CALLBACK 4 int inprogress; int isconnected; } CONN; static CONN *connections; /* -------------------------------------------------------------------- */ static CONN *conn_remember(capi_connection *conn, int type) { CONN *p, **pp; for (pp = &connections; *pp; pp = &(*pp)->next) ; if ((p = (CONN *)malloc(sizeof(CONN))) == 0) { int serrno = errno; fatal("malloc failed - %s (%d)", strerror(serrno), serrno); return 0; } memset(p, 0, sizeof(CONN)); p->conn = conn; p->type = type; p->next = 0; switch (type) { case CONNTYPE_OUTGOING: case CONNTYPE_INCOMING: case CONNTYPE_FOR_CALLBACK: p->inprogress = 1; p->isconnected = 0; break; default: break; } *pp = p; return p; } static int conn_forget(capi_connection *conn) { CONN **pp, *p; for (pp = &connections; *pp && (*pp)->conn != conn; pp = &(*pp)->next) ; if (*pp) { p = *pp; *pp = (*pp)->next; free(p); return 0; } return -1; } static CONN *conn_find(capi_connection *cp) { CONN *p; for (p = connections; p; p = p->next) { if (p->conn == cp) return p; } return 0; } /* -------------------------------------------------------------------- */ static int conn_connected(capi_connection *conn) { CONN *p; for (p = connections; p; p = p->next) { if (p->conn == conn) { p->isconnected = 1; p->inprogress = 0; return 0; } } fatal("connected connection not found ??"); return -1; } /* -------------------------------------------------------------------- */ static int conn_incoming_connected(void) { CONN *p; for (p = connections; p; p = p->next) { if (p->type == CONNTYPE_INCOMING) return p->isconnected; } return 0; } static int conn_incoming_exists(void) { CONN *p; for (p = connections; p; p = p->next) { if (p->type == CONNTYPE_INCOMING) return p->isconnected || p->inprogress; } return 0; } /* -------------------------------------------------------------------- */ static int conn_inprogress(capi_connection *cp) { CONN *p; for (p = connections; p; p = p->next) { if (p->conn == cp) return p->inprogress; } return 0; } static int conn_isconnected(capi_connection *cp) { CONN *p; if (cp) { for (p = connections; p; p = p->next) { if (p->conn == cp) return p->isconnected; } } else { for (p = connections; p; p = p->next) { if (p->isconnected) return 1; } } return 0; } /* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */ static int act_controller = 1; static void reset_contr_status(void) { act_controller = 1; } static int get_next_contr(void) { STRINGLIST *sl; int contr, count = 0; if (!controllers) { /* one controller only */ return(controller); } for (sl = controllers; sl; sl = sl->next) { count++; contr = strtol(sl->s, NULL, 10); if (count == act_controller) { act_controller++; return(contr); } } act_controller = 1; return(strtol(controllers->s, NULL, 10)); } static void do_listen(unsigned cipmask, unsigned cipmask2) { STRINGLIST *sl; int contr; if (!controllers) { /* one controller only */ (void) capiconn_listen(ctx, controller, cipmask, cipmask2); return; } for (sl = controllers; sl; sl = sl->next) { contr = strtol(sl->s, NULL, 10); (void) capiconn_listen(ctx, contr, cipmask, cipmask2); } } /* -------------------------------------------------------------------- */ /* -------- demand & wakeup ------------------------------------------- */ /* -------------------------------------------------------------------- */ static void setupincoming_for_demand(void) { do_listen(cipmask, 0); info("waiting for demand wakeup ..."); } static void wakeupdemand(void) { if (curphase != PHASE_DORMANT) { info("wakeup not possible in phase %s, delayed", phase2str(curphase)); wakeupnow++; return; } capi_wake_up(); } /* -------------------------------------------------------------------- */ /* -------- CAPI setup & handling ------------------------------------- */ /* -------------------------------------------------------------------- */ static void init_capiconn(void) { static int init = 0; STRINGLIST *sl; int contr; if (init) { do_listen(0, 0); return; } init = 1; if (!controllers) { /* one controller only */ if (capiconn_addcontr(ctx, controller, &cinfo) != CAPICONN_OK) { (void)capiconn_freecontext(ctx); (void)capi20_release(applid); fatal("add controller %d failed", controller); return; } if (cinfo.ddi) dbglog("contr=%d ddi=\"%s\" n=%d", controller, cinfo.ddi, cinfo.ndigits); else dbglog("contr=%d", controller); } else { for (sl = controllers; sl; sl = sl->next) { contr = strtol(sl->s, NULL, 10); if (capiconn_addcontr(ctx, contr, &cinfo) != CAPICONN_OK) { (void)capiconn_freecontext(ctx); (void)capi20_release(applid); fatal("add controller %d failed", contr); return; } if (cinfo.ddi) dbglog("contr=%d ddi=\"%s\" n=%d", contr, cinfo.ddi, cinfo.ndigits); else dbglog("contr=%d", contr); } } do_listen(0, 0); } /* -------------------------------------------------------------------- */ void handle_capi_message(void) { unsigned char *msg = 0; if (capi20_get_message(applid, &msg) == 0) capiconn_inject(applid, msg); } static void handlemessages(void) { unsigned char *msg = 0; struct timeval tv; tv.tv_sec = 1; tv.tv_usec = 0; if (capi20_waitformessage(applid, &tv) == 0) { if (capi20_get_message(applid, &msg) == 0) capiconn_inject(applid, msg); } } /* -------------------------------------------------------------------- */ static void dodisconnect(capi_connection *cp) { CONN *conn; time_t t; if ((conn = conn_find(cp)) == 0) return; (void)capiconn_disconnect(cp, 0); conn->isconnected = conn->inprogress = 0; t = time(0)+10; do { handlemessages(); } while (conn_find(cp) && time(0) < t); if (conn_find(cp)) fatal("timeout while waiting for disconnect"); } static void disconnectall(void) { time_t t; CONN *p; do_listen(0, 0); for (p = connections; p; p = p->next) { if (p->inprogress || p->isconnected) { p->isconnected = p->inprogress = 0; capiconn_disconnect(p->conn, 0); } } t = time(0)+10; do { handlemessages(); } while (connections && time(0) < t); if (connections && !exiting) fatal("disconnectall failed"); } /* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */ static char *conninfo(capi_connection *p) { static char buf[1024]; capi_conninfo *cp = capiconn_getinfo(p); char *callingnumber = ""; char *callednumber = ""; if (cp->callingnumber && cp->callingnumber[0] > 2) callingnumber = cp->callingnumber+3; if (cp->callednumber && cp->callednumber[0] > 1) callednumber = cp->callednumber+2; snprintf(buf, sizeof(buf), "\"%s\" -> \"%s\" %s (pcli=0x%x/ncci=0x%x)", callingnumber, callednumber, cp->isincoming ? "incoming" : "outgoing", cp->plci, cp->ncci ); buf[sizeof(buf)-1] = 0; return buf; } /* -------------------------------------------------------------------- */ /* -------- reject reason handling (wuerg) ---------------------------- */ /* -------------------------------------------------------------------- */ static unsigned dreason = 0; static int was_no_reject(void) { if ((dreason & 0xff00) != 0x3400) return 1; switch (dreason) { case 0x34a2: /* No circuit / channel available */ return 1; } return 0; } /* -------------------------------------------------------------------- */ /* -------- disconnect handler ---------------------------------------- */ /* -------------------------------------------------------------------- */ static void disconnected(capi_connection *cp, int localdisconnect, unsigned reason, unsigned reason_b3) { CONN *p; if ((p = conn_find(cp)) == 0) return; conn_forget(cp); capi_is_disconnected(cp); switch (p->type) { case CONNTYPE_OUTGOING: case CONNTYPE_INCOMING: break; case CONNTYPE_IGNORE: case CONNTYPE_REJECT: return; case CONNTYPE_FOR_CALLBACK: dreason = reason; break; } if (reason != 0x3304) /* Another Applikation got the call */ info("disconnect(%s): %s 0x%04x (0x%04x) - %s", localdisconnect ? "local" : "remote", conninfo(cp), reason, reason_b3, capi_info2str(reason)); } /* -------------------------------------------------------------------- */ /* -------- incoming call handler ------------------------------------- */ /* -------------------------------------------------------------------- */ static void incoming(capi_connection *cp, unsigned contr, unsigned cipvalue, char *callednumber, char *callingnumber) { STRINGLIST *p; char *s; info("incoming call: %s (0x%x)", conninfo(cp), cipvalue); if (conn_incoming_exists()) { info("ignoring call, incoming connection exists"); conn_remember(cp, CONNTYPE_IGNORE); (void) capiconn_ignore(cp); return; } if (opt_inmsn) { for (p = inmsns; p; p = p->next) { if ( (s = strstr(callednumber, p->s)) != 0 && strcmp(s, p->s) == 0) break; } if (!p) { info("ignoring call, msn %s not in \"%s\"", callednumber, opt_inmsn); conn_remember(cp, CONNTYPE_IGNORE); (void) capiconn_ignore(cp); return; } } else if (opt_msn) { if ( (s = strstr(callednumber, opt_msn)) == 0 || strcmp(s, opt_msn) != 0) { info("ignoring call, msn mismatch (%s != %s)", opt_msn, callednumber); conn_remember(cp, CONNTYPE_IGNORE); (void) capiconn_ignore(cp); return; } } if (opt_cli) { for (p = clis; p; p = p->next) { if (shmatch(callingnumber, p->s)) break; } if (!p) { info("ignoring call, cli mismatch (%s != %s)", opt_cli, callingnumber); conn_remember(cp, CONNTYPE_IGNORE); (void) capiconn_ignore(cp); return; } } else if (opt_number) { for (p = numbers; p; p = p->next) { if ( (s = strstr(callingnumber, p->s)) != 0 && strcmp(s, p->s) == 0) break; } if (!p) { info("ignoring call, number mismatch (%s != %s)", opt_number, callingnumber); conn_remember(cp, CONNTYPE_IGNORE); (void) capiconn_ignore(cp); return; } } else if (opt_acceptdelayflag) { /* * non cli or number match, * give more specific listen a chance (bad) */ info("accept delayed, no cli or number match"); sleep(1); } switch (cipvalue) { case 1: /* Speech */ case 4: /* 3.1 kHz audio */ case 5: /* 7 kHz audio */ case 16: /* Telephony */ case 26: /* 7 kHz telephony */ if (opt_voicecallwakeup) { goto wakeupdemand; } else { info("ignoring speech call from %s", callingnumber); conn_remember(cp,CONNTYPE_IGNORE); (void) capiconn_ignore(cp); } break; case 2: /* Unrestricted digital information */ case 3: /* Restricted digital information */ if (proto == PROTO_HDLC) { if (demand) goto wakeupmatch; if (coso == COSO_LOCAL) goto callback; goto accept; } else if (proto == PROTO_X75) { if (demand) goto wakeupmatch; if (coso == COSO_LOCAL) goto callback; goto accept; } else { info("ignoring digital call from %s", callingnumber); conn_remember(cp,CONNTYPE_IGNORE); (void) capiconn_ignore(cp); } break; case 17: /* Group 2/3 facsimile */ info("ignoring fax call from %s", callingnumber); conn_remember(cp,CONNTYPE_IGNORE); (void) capiconn_ignore(cp); break; default: info("ignoring type %d call from %s", cipvalue, callingnumber); conn_remember(cp,CONNTYPE_IGNORE); (void) capiconn_ignore(cp); break; } return; callback: do_listen(0, 0); dbglog("rejecting call: %s (0x%x)", conninfo(cp), cipvalue); conn_remember(cp, CONNTYPE_REJECT); capiconn_reject(cp); makecallback(); return; wakeupdemand: dbglog("rejecting call: %s (0x%x)", conninfo(cp), cipvalue); conn_remember(cp, CONNTYPE_REJECT); capiconn_reject(cp); wakeupdemand(); return; wakeupmatch: if (coso == COSO_LOCAL) goto callback; accept: switch (proto) { default: case PROTO_HDLC: (void) capiconn_accept(cp, 0, 1, 0, 0, 0, 0, 0); break; case PROTO_X75: (void) capiconn_accept(cp, 0, 0, 0, 0, 0, 0, 0); break; } conn_remember(cp, CONNTYPE_INCOMING); do_listen(0, 0); return; } /* -------------------------------------------------------------------- */ /* -------- connection established handler ---------------------------- */ /* -------------------------------------------------------------------- */ static void connected(capi_connection *cp, _cstruct NCPI) { info("connected: %s", conninfo(cp)); if (conn_incoming_connected()) { time_t t = time(0)+opt_connectdelay; do { handlemessages(); if (status != EXIT_OK) die(status); } while (time(0) < t); } capi_is_connected(cp); conn_connected(cp); if (curphase == PHASE_DORMANT) wakeupdemand(); } /* -------------------------------------------------------------------- */ /* -------- charge information ---------------------------------------- */ /* -------------------------------------------------------------------- */ void chargeinfo(capi_connection *cp, unsigned long charge, int inunits) { if (inunits) { info("%s: charge in units: %d", conninfo(cp), charge); } else { info("%s: charge in currency: %d", conninfo(cp), charge); } } /* -------------------------------------------------------------------- */ /* -------- tranfer capi message to CAPI ------------------------------ */ /* -------------------------------------------------------------------- */ void put_message(unsigned appid, unsigned char *msg) { unsigned err; err = capi20_put_message (appid, msg); if (err && !exiting) fatal("putmessage(appid=%d) - %s 0x%x", appid, capi_info2str(err), err); } /* -------------------------------------------------------------------- */ /* -------- capiconn module setup ------------------------------------- */ /* -------------------------------------------------------------------- */ capiconn_callbacks callbacks = { malloc: malloc, free: free, disconnected: disconnected, incoming: incoming, connected: connected, received: handle_capi_data, datasent: handle_capi_sent, chargeinfo: chargeinfo, capi_put_message: put_message, debugmsg: (void (*)(const char *, ...))dbglog, infomsg: (void (*)(const char *, ...))info, errmsg: (void (*)(const char *, ...))error }; /* -------------------------------------------------------------------- */ /* -------- create a connection --------------------------------------- */ /* -------------------------------------------------------------------- */ static capi_connection *setupconnection(int contr, char *num, int awaitingreject) { struct capi_connection *cp; char number[256]; snprintf(number, sizeof(number), "%s%s", opt_numberprefix ? opt_numberprefix : "", num); if (proto == PROTO_HDLC) { cp = capiconn_connect(ctx, contr, /* contr */ 2, /* cipvalue */ opt_channels ? 0 : number, opt_channels ? 0 : opt_msn, 0, 1, 0, 0, 0, 0, opt_channels ? AdditionalInfo : 0, 0); } else if (proto == PROTO_X75) { cp = capiconn_connect(ctx, contr, /* contr */ 2, /* cipvalue */ opt_channels ? 0 : number, opt_channels ? 0 : opt_msn, 0, 0, 0, 0, 0, 0, opt_channels ? AdditionalInfo : 0, 0); } else { fatal("unknown protocol \"%s\"", opt_proto); return 0; } if (opt_channels) { info("leased line (%s)", opt_proto); } else if (awaitingreject) { info("dialing %s (awaiting reject)", number); } else { info("dialing %s (%s)", number, opt_proto); } if (awaitingreject) conn_remember(cp, CONNTYPE_FOR_CALLBACK); else conn_remember(cp, CONNTYPE_OUTGOING); return cp; } /* -------------------------------------------------------------------- */ /* -------- connect leased line --------------------------------------- */ /* -------------------------------------------------------------------- */ static void makeleasedline(void) { capi_connection *cp; int retry = 0; time_t t; reset_contr_status(); do { if (retry) { t = time(0)+opt_redialdelay; do { handlemessages(); if (status != EXIT_OK) die(status); } while (time(0) < t); } cp = setupconnection(get_next_contr(), "", 0); t = time(0)+opt_dialtimeout; do { handlemessages(); if (status != EXIT_OK) { if (conn_find(cp)) { info("status %d, disconnecting ...", status); dodisconnect(cp); } else { die(status); } } } while (time(0) < t && conn_inprogress(cp)); if (conn_isconnected(cp)) goto connected; if (status != EXIT_OK) die(status); } while (++retry < opt_dialmax); connected: if (conn_isconnected(cp)) { t = time(0)+opt_connectdelay; do { handlemessages(); if (status != EXIT_OK) die(status); } while (time(0) < t); } if (status != EXIT_OK) die(status); if (!conn_isconnected(cp)) fatal("couldn't make connection"); } /* -------------------------------------------------------------------- */ /* -------- connect a dialup connection ------------------------------- */ /* -------------------------------------------------------------------- */ static void makeconnection(STRINGLIST *numbers) { capi_connection *cp = 0; time_t t; STRINGLIST *p; int retry = 0; reset_contr_status(); do { for (p = numbers; p; p = p->next) { if (retry || p != numbers) { t = time(0)+opt_redialdelay; do { handlemessages(); if (status != EXIT_OK) die(status); } while (time(0) < t); } cp = setupconnection(get_next_contr(), p->s, 0); t = time(0)+opt_dialtimeout; do { handlemessages(); if (status != EXIT_OK) { if (conn_find(cp)) { info("status %d, disconnecting ...", status); dodisconnect(cp); } else { die(status); } } } while (time(0) < t && conn_inprogress(cp)); if (conn_isconnected(cp)) goto connected; if (status != EXIT_OK) die(status); } } while (++retry < opt_dialmax); connected: if (conn_isconnected(cp)) { t = time(0)+opt_connectdelay; do { handlemessages(); if (status != EXIT_OK) die(status); } while (time(0) < t); } if (!conn_isconnected(cp)) error("couldn't make connection after %d retries", retry); } /* -------------------------------------------------------------------- */ /* -------- dial and wait for callback -------------------------------- */ /* -------------------------------------------------------------------- */ static void makeconnection_with_callback(void) { capi_connection *cp; STRINGLIST *p; time_t t; int retry = 0; reset_contr_status(); do { for (p = numbers; p; p = p->next) { if (retry || p != numbers) { again: t = time(0)+opt_redialdelay; do { handlemessages(); if (status != EXIT_OK) die(status); } while (time(0) < t); } cp = setupconnection(get_next_contr(), p->s, 1); /* Wait specific time for the server rejecting the call */ t = time(0)+opt_dialtimeout; do { handlemessages(); if (status != EXIT_OK) die(status); } while (time(0) < t && conn_inprogress(cp)); if (conn_inprogress(cp)) { (void)capiconn_disconnect(cp, 0); dreason = 0x3490; } if (conn_isconnected(cp)) { dodisconnect(cp); error("callback failed - other party answers the call (no reject)"); } else if (was_no_reject()) { goto again; } else { do_listen(cipmask, 0); info("waiting for callback..."); /* Wait for server calling back */ t = time(0)+opt_cbwait; do { handlemessages(); if (status != EXIT_OK) { do_listen(0, 0); die(status); } } while (!conn_incoming_connected() && time(0) < t); if (conn_incoming_connected()) { return; } info("callback failed (no call)"); } } } while (++retry < opt_dialmax); error("callback failed (no call)"); } /* -------------------------------------------------------------------- */ /* -------- execute a callback ---------------------------------------- */ /* -------------------------------------------------------------------- */ static void makecallback(void) { time_t t; t = time(0)+opt_cbdelay; do { handlemessages(); if (status != EXIT_OK) die(status); } while (time(0) < t); if (callbacknumbers) makeconnection(callbacknumbers); else makeconnection(numbers); } /* -------------------------------------------------------------------- */ /* -------- wait for an incoming call --------------------------------- */ /* -------------------------------------------------------------------- */ static void waitforcall(void) { do_listen(cipmask, 0); info("waiting for incoming call ..."); } /* -------------------------------------------------------------------- */ /* -------- PPPD state change hook ------------------------------------ */ /* -------------------------------------------------------------------- */ static char *phase2str(int phase) { static struct tmpbuf { struct tmpbuf *next; char buf[32]; } buffer[] = { { &buffer[1] }, { &buffer[2] }, { &buffer[0] }, }; static struct tmpbuf *p = &buffer[0]; switch (phase) { case PHASE_DEAD: return "dead"; case PHASE_INITIALIZE: return "initialize"; case PHASE_SERIALCONN: return "serialconn"; case PHASE_DORMANT: return "dormant"; case PHASE_ESTABLISH: return "establish"; case PHASE_AUTHENTICATE: return "authenticate"; case PHASE_CALLBACK: return "callback"; case PHASE_NETWORK: return "network"; case PHASE_RUNNING: return "running"; case PHASE_TERMINATE: return "terminate"; case PHASE_DISCONNECT: return "disconnect"; case PHASE_HOLDOFF: return "holdoff"; } p = p->next; sprintf(p->buf,"unknown-%d", phase); return p->buf; } /* -------------------------------------------------------------------- */ int capi_new_phase(int phase) { if (phase == curphase) { info("phase %s, again.", phase2str(phase)); return 0; } if (curphase != -1) { info("phase %s (was %s).", phase2str(phase), phase2str(curphase)); } else { info("phase %s.", phase2str(phase)); } curphase = phase; switch (phase) { case PHASE_INITIALIZE: case PHASE_ESTABLISH: case PHASE_AUTHENTICATE: case PHASE_CALLBACK: case PHASE_NETWORK: case PHASE_RUNNING: case PHASE_TERMINATE: case PHASE_DISCONNECT: case PHASE_HOLDOFF: break; case PHASE_DEAD: disconnectall(); break; case PHASE_DORMANT: status = EXIT_OK; itun_check_options(); init_capiconn(); if (opt_inmsn || opt_cli) { if (wakeupnow) wakeupdemand(); setupincoming_for_demand(); } break; case PHASE_SERIALCONN: status = EXIT_OK; wakeupnow = 0; if (conn_isconnected(0)) break; itun_check_options(); init_capiconn(); if (opt_number) { if (coso == COSO_REMOTE) { makeconnection_with_callback(); } else { makeconnection(numbers); } } else if (opt_channels) { makeleasedline(); } else { waitforcall(); } break; } return 0; } /* -------------------------------------------------------------------- */ /* -------- init/exit function ---------------------------------------- */ /* -------------------------------------------------------------------- */ int itun_init(void) { int serrno; int err; if ((err = capi20_register (1, 8, 2048, &applid)) != 0) { serrno = errno; fatal("CAPI_REGISTER failed - %s (0x%04x) [%s (%d)]", capi_info2str(err), err, strerror(serrno), errno); return(-1); } if ((ctx = capiconn_getcontext(applid, &callbacks)) == 0) { (void)capi20_release(applid); fatal("get_context failed"); return(-1); } info("init"); return(capi20_fileno(applid)); } void itun_exit(void) { exiting = 1; disconnectall(); (void)capi20_release(applid); info("exit"); }