| /*- |
| * Copyright (c) 1998 Brian Somers <brian@Awfulhak.org> |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| * |
| * $FreeBSD: src/usr.sbin/ppp/datalink.c,v 1.77.26.1 2010/12/21 17:10:29 kensmith Exp $ |
| */ |
| |
| #include <sys/param.h> |
| #include <netinet/in.h> |
| #include <netinet/in_systm.h> |
| #include <netinet/ip.h> |
| #include <sys/socket.h> |
| #include <sys/un.h> |
| |
| #include <ctype.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/uio.h> |
| #include <termios.h> |
| |
| #include "layer.h" |
| #include "mbuf.h" |
| #include "log.h" |
| #include "defs.h" |
| #include "timer.h" |
| #include "fsm.h" |
| #include "descriptor.h" |
| #include "lqr.h" |
| #include "hdlc.h" |
| #include "lcp.h" |
| #include "async.h" |
| #include "throughput.h" |
| #include "ccp.h" |
| #include "link.h" |
| #include "physical.h" |
| #include "iplist.h" |
| #include "slcompress.h" |
| #include "ncpaddr.h" |
| #include "ipcp.h" |
| #include "filter.h" |
| #include "mp.h" |
| #ifndef NORADIUS |
| #include "radius.h" |
| #endif |
| #include "ipv6cp.h" |
| #include "ncp.h" |
| #include "bundle.h" |
| #include "chat.h" |
| #include "auth.h" |
| #include "prompt.h" |
| #include "proto.h" |
| #include "pap.h" |
| #include "chap.h" |
| #include "command.h" |
| #include "cbcp.h" |
| #include "datalink.h" |
| |
| static void datalink_LoginDone(struct datalink *); |
| static void datalink_NewState(struct datalink *, unsigned); |
| static char *datalink_NextName(struct datalink *); |
| |
| static void |
| datalink_OpenTimeout(void *v) |
| { |
| struct datalink *dl = (struct datalink *)v; |
| |
| timer_Stop(&dl->dial.timer); |
| if (dl->state == DATALINK_OPENING) |
| log_Printf(LogCHAT, "%s: Redial timer expired.\n", dl->name); |
| } |
| |
| static int |
| datalink_StartDialTimer(struct datalink *dl, int Timeout) |
| { |
| int result = Timeout; |
| |
| timer_Stop(&dl->dial.timer); |
| if (Timeout < 0) |
| result = (random() % DIAL_TIMEOUT) + 1; |
| dl->dial.timer.load = result ? result * SECTICKS : 1; |
| dl->dial.timer.func = datalink_OpenTimeout; |
| dl->dial.timer.name = "dial"; |
| dl->dial.timer.arg = dl; |
| timer_Start(&dl->dial.timer); |
| if (dl->state == DATALINK_OPENING) |
| log_Printf(LogPHASE, "%s: Enter pause (%d) for redialing.\n", |
| dl->name, result); |
| return result; |
| } |
| |
| static void |
| datalink_HangupDone(struct datalink *dl) |
| { |
| if (dl->physical->type == PHYS_DEDICATED && !dl->bundle->CleaningUp && |
| dl->physical->fd != -1) { |
| /* Don't close our device if the link is dedicated */ |
| datalink_LoginDone(dl); |
| return; |
| } |
| |
| chat_Finish(&dl->chat); |
| physical_Close(dl->physical); |
| dl->phone.chosen = "N/A"; |
| |
| if (dl->cbcp.required) { |
| log_Printf(LogPHASE, "Call peer back on %s\n", dl->cbcp.fsm.phone); |
| dl->cfg.callback.opmask = 0; |
| strncpy(dl->cfg.phone.list, dl->cbcp.fsm.phone, |
| sizeof dl->cfg.phone.list - 1); |
| dl->cfg.phone.list[sizeof dl->cfg.phone.list - 1] = '\0'; |
| dl->phone.alt = dl->phone.next = NULL; |
| dl->reconnect_tries = dl->cfg.reconnect.max; |
| dl->dial.tries = dl->cfg.dial.max; |
| dl->dial.incs = 0; |
| dl->script.run = 1; |
| dl->script.packetmode = 1; |
| if (!physical_SetMode(dl->physical, PHYS_BACKGROUND)) |
| log_Printf(LogERROR, "Oops - can't change mode to BACKGROUND (gulp) !\n"); |
| bundle_LinksRemoved(dl->bundle); |
| /* if dial.timeout is < 0 (random), we don't override fsm.delay */ |
| if (dl->cbcp.fsm.delay < dl->cfg.dial.timeout) |
| dl->cbcp.fsm.delay = dl->cfg.dial.timeout; |
| datalink_StartDialTimer(dl, dl->cbcp.fsm.delay); |
| cbcp_Down(&dl->cbcp); |
| datalink_NewState(dl, DATALINK_OPENING); |
| if (bundle_Phase(dl->bundle) == PHASE_DEAD || |
| bundle_Phase(dl->bundle) == PHASE_TERMINATE) |
| bundle_NewPhase(dl->bundle, PHASE_ESTABLISH); |
| } else if (dl->bundle->CleaningUp || |
| (dl->physical->type == PHYS_DIRECT) || |
| ((!dl->dial.tries || (dl->dial.tries < 0 && !dl->reconnect_tries)) && |
| !(dl->physical->type & (PHYS_DDIAL|PHYS_DEDICATED)))) { |
| datalink_NewState(dl, DATALINK_CLOSED); |
| dl->dial.tries = -1; |
| dl->dial.incs = 0; |
| dl->reconnect_tries = 0; |
| bundle_LinkClosed(dl->bundle, dl); |
| if (!dl->bundle->CleaningUp && |
| !(dl->physical->type & (PHYS_DIRECT|PHYS_BACKGROUND|PHYS_FOREGROUND))) |
| datalink_StartDialTimer(dl, datalink_GetDialTimeout(dl)); |
| } else { |
| datalink_NewState(dl, DATALINK_OPENING); |
| if (bundle_Phase(dl->bundle) == PHASE_DEAD || |
| bundle_Phase(dl->bundle) == PHASE_TERMINATE) |
| bundle_NewPhase(dl->bundle, PHASE_ESTABLISH); |
| if (dl->dial.tries < 0) { |
| datalink_StartDialTimer(dl, dl->cfg.reconnect.timeout); |
| dl->dial.tries = dl->cfg.dial.max; |
| dl->dial.incs = 0; |
| dl->reconnect_tries--; |
| log_Printf(LogCHAT, "%s: Reconnect try %d of %d\n", |
| dl->name, dl->cfg.reconnect.max - dl->reconnect_tries, |
| dl->cfg.reconnect.max); |
| bundle_Notify(dl->bundle, EX_RECONNECT); |
| } else { |
| if (dl->phone.next == NULL) |
| datalink_StartDialTimer(dl, datalink_GetDialTimeout(dl)); |
| else |
| datalink_StartDialTimer(dl, dl->cfg.dial.next_timeout); |
| bundle_Notify(dl->bundle, EX_REDIAL); |
| } |
| } |
| } |
| |
| const char * |
| datalink_ChoosePhoneNumber(struct datalink *dl) |
| { |
| char *phone; |
| |
| if (dl->phone.alt == NULL) { |
| if (dl->phone.next == NULL) { |
| strncpy(dl->phone.list, dl->cfg.phone.list, sizeof dl->phone.list - 1); |
| dl->phone.list[sizeof dl->phone.list - 1] = '\0'; |
| if (*dl->phone.list == '\0') |
| return ""; |
| dl->phone.next = dl->phone.list; |
| } |
| dl->phone.alt = strsep(&dl->phone.next, ":"); |
| } |
| phone = strsep(&dl->phone.alt, "|"); |
| dl->phone.chosen = *phone ? phone : "[NONE]"; |
| if (*phone) |
| log_Printf(LogCHAT, "Phone: %s\n", phone); |
| return phone; |
| } |
| |
| static void |
| datalink_LoginDone(struct datalink *dl) |
| { |
| chat_Finish(&dl->chat); |
| |
| if (!dl->script.packetmode) { |
| dl->dial.tries = -1; |
| dl->dial.incs = 0; |
| datalink_NewState(dl, DATALINK_READY); |
| } else if (!physical_Raw(dl->physical)) { |
| dl->dial.tries = 0; |
| log_Printf(LogWARN, "datalink_LoginDone: Not connected.\n"); |
| if (dl->script.run) { |
| datalink_NewState(dl, DATALINK_LOGOUT); |
| if (!chat_Setup(&dl->chat, dl->cfg.script.logout, NULL)) |
| log_Printf(LogWARN, "Invalid logout script\n"); |
| } else { |
| physical_StopDeviceTimer(dl->physical); |
| if (dl->physical->type == PHYS_DEDICATED) |
| /* force a redial timeout */ |
| physical_Close(dl->physical); |
| datalink_HangupDone(dl); |
| } |
| } else { |
| dl->dial.tries = -1; |
| dl->dial.incs = 0; |
| |
| hdlc_Init(&dl->physical->hdlc, &dl->physical->link.lcp); |
| async_Setup(&dl->physical->async); |
| |
| lcp_Setup(&dl->physical->link.lcp, dl->state == DATALINK_READY ? |
| 0 : dl->physical->link.lcp.cfg.openmode); |
| ccp_Setup(&dl->physical->link.ccp); |
| |
| datalink_NewState(dl, DATALINK_LCP); |
| fsm_Up(&dl->physical->link.lcp.fsm); |
| fsm_Open(&dl->physical->link.lcp.fsm); |
| } |
| } |
| |
| static int |
| datalink_UpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w, fd_set *e, |
| int *n) |
| { |
| struct datalink *dl = descriptor2datalink(d); |
| int result; |
| |
| result = 0; |
| switch (dl->state) { |
| case DATALINK_CLOSED: |
| if ((dl->physical->type & (PHYS_DIRECT|PHYS_DEDICATED|PHYS_BACKGROUND| |
| PHYS_FOREGROUND|PHYS_DDIAL)) && |
| !dl->bundle->CleaningUp) |
| /* |
| * Our first time in - DEDICATED & DDIAL never come down, and |
| * DIRECT, FOREGROUND & BACKGROUND get deleted when they enter |
| * DATALINK_CLOSED. Go to DATALINK_OPENING via datalink_Up() |
| * and fall through. |
| */ |
| datalink_Up(dl, 1, 1); |
| else |
| break; |
| /* FALLTHROUGH */ |
| |
| case DATALINK_OPENING: |
| if (dl->dial.timer.state != TIMER_RUNNING) { |
| if (--dl->dial.tries < 0) |
| dl->dial.tries = 0; |
| if (physical_Open(dl->physical) >= 0) { |
| log_WritePrompts(dl, "%s: Entering terminal mode on %s\r\n" |
| "Type `~?' for help\r\n", dl->name, |
| dl->physical->name.full); |
| if (dl->script.run) { |
| datalink_NewState(dl, DATALINK_DIAL); |
| if (!chat_Setup(&dl->chat, dl->cfg.script.dial, |
| *dl->cfg.script.dial ? |
| datalink_ChoosePhoneNumber(dl) : "")) |
| log_Printf(LogWARN, "Invalid dial script\n"); |
| if (!(dl->physical->type & (PHYS_DDIAL|PHYS_DEDICATED)) && |
| dl->cfg.dial.max) |
| log_Printf(LogCHAT, "%s: Dial attempt %u of %d\n", |
| dl->name, dl->cfg.dial.max - dl->dial.tries, |
| dl->cfg.dial.max); |
| } else |
| datalink_NewState(dl, DATALINK_CARRIER); |
| return datalink_UpdateSet(d, r, w, e, n); |
| } else { |
| if (!(dl->physical->type & (PHYS_DDIAL|PHYS_DEDICATED)) && |
| dl->cfg.dial.max) |
| log_Printf(LogCHAT, "Failed to open device (attempt %u of %d)\n", |
| dl->cfg.dial.max - dl->dial.tries, dl->cfg.dial.max); |
| else |
| log_Printf(LogCHAT, "Failed to open device\n"); |
| |
| if (dl->bundle->CleaningUp || |
| (!(dl->physical->type & (PHYS_DDIAL|PHYS_DEDICATED)) && |
| dl->cfg.dial.max && dl->dial.tries == 0)) { |
| datalink_NewState(dl, DATALINK_CLOSED); |
| dl->reconnect_tries = 0; |
| dl->dial.tries = -1; |
| log_WritePrompts(dl, "Failed to open %s\n", |
| dl->physical->name.full); |
| bundle_LinkClosed(dl->bundle, dl); |
| } |
| if (!dl->bundle->CleaningUp) { |
| int timeout; |
| |
| timeout = datalink_StartDialTimer(dl, datalink_GetDialTimeout(dl)); |
| bundle_Notify(dl->bundle, EX_REDIAL); |
| log_WritePrompts(dl, "Failed to open %s, pause %d seconds\n", |
| dl->physical->name.full, timeout); |
| } |
| } |
| } |
| break; |
| |
| case DATALINK_CARRIER: |
| /* Wait for carrier on the device */ |
| switch (physical_AwaitCarrier(dl->physical)) { |
| case CARRIER_PENDING: |
| log_Printf(LogDEBUG, "Waiting for carrier\n"); |
| return 0; /* A device timer is running to wake us up again */ |
| |
| case CARRIER_OK: |
| if (dl->script.run) { |
| datalink_NewState(dl, DATALINK_LOGIN); |
| if (!chat_Setup(&dl->chat, dl->cfg.script.login, NULL)) |
| log_Printf(LogWARN, "Invalid login script\n"); |
| } else |
| datalink_LoginDone(dl); |
| return datalink_UpdateSet(d, r, w, e, n); |
| |
| case CARRIER_LOST: |
| physical_Offline(dl->physical); /* Is this required ? */ |
| if (dl->script.run) { |
| datalink_NewState(dl, DATALINK_HANGUP); |
| if (!chat_Setup(&dl->chat, dl->cfg.script.hangup, NULL)) |
| log_Printf(LogWARN, "Invalid hangup script\n"); |
| return datalink_UpdateSet(d, r, w, e, n); |
| } else { |
| datalink_HangupDone(dl); |
| return 0; /* Maybe bundle_CleanDatalinks() has something to do */ |
| } |
| } |
| |
| case DATALINK_HANGUP: |
| case DATALINK_DIAL: |
| case DATALINK_LOGOUT: |
| case DATALINK_LOGIN: |
| result = descriptor_UpdateSet(&dl->chat.desc, r, w, e, n); |
| switch (dl->chat.state) { |
| case CHAT_DONE: |
| /* script succeeded */ |
| switch(dl->state) { |
| case DATALINK_HANGUP: |
| datalink_HangupDone(dl); |
| break; |
| case DATALINK_DIAL: |
| datalink_NewState(dl, DATALINK_CARRIER); |
| return datalink_UpdateSet(d, r, w, e, n); |
| case DATALINK_LOGOUT: |
| datalink_NewState(dl, DATALINK_HANGUP); |
| physical_Offline(dl->physical); |
| if (!chat_Setup(&dl->chat, dl->cfg.script.hangup, NULL)) |
| log_Printf(LogWARN, "Invalid hangup script\n"); |
| return datalink_UpdateSet(d, r, w, e, n); |
| case DATALINK_LOGIN: |
| dl->phone.alt = NULL; |
| datalink_LoginDone(dl); |
| return datalink_UpdateSet(d, r, w, e, n); |
| } |
| break; |
| case CHAT_FAILED: |
| /* Going down - script failed */ |
| log_Printf(LogWARN, "Chat script failed\n"); |
| switch(dl->state) { |
| case DATALINK_HANGUP: |
| datalink_HangupDone(dl); |
| break; |
| case DATALINK_DIAL: |
| case DATALINK_LOGOUT: |
| case DATALINK_LOGIN: |
| datalink_NewState(dl, DATALINK_HANGUP); |
| physical_Offline(dl->physical); |
| if (!chat_Setup(&dl->chat, dl->cfg.script.hangup, NULL)) |
| log_Printf(LogWARN, "Invalid hangup script\n"); |
| return datalink_UpdateSet(d, r, w, e, n); |
| } |
| break; |
| } |
| break; |
| |
| case DATALINK_READY: |
| case DATALINK_LCP: |
| case DATALINK_AUTH: |
| case DATALINK_CBCP: |
| case DATALINK_OPEN: |
| result = descriptor_UpdateSet(&dl->chap.desc, r, w, e, n) + |
| descriptor_UpdateSet(&dl->physical->desc, r, w, e, n); |
| break; |
| } |
| return result; |
| } |
| |
| int |
| datalink_RemoveFromSet(struct datalink *dl, fd_set *r, fd_set *w, fd_set *e) |
| { |
| return physical_RemoveFromSet(dl->physical, r, w, e); |
| } |
| |
| static int |
| datalink_IsSet(struct fdescriptor *d, const fd_set *fdset) |
| { |
| struct datalink *dl = descriptor2datalink(d); |
| |
| switch (dl->state) { |
| case DATALINK_CLOSED: |
| case DATALINK_OPENING: |
| break; |
| |
| case DATALINK_HANGUP: |
| case DATALINK_DIAL: |
| case DATALINK_LOGOUT: |
| case DATALINK_LOGIN: |
| return descriptor_IsSet(&dl->chat.desc, fdset); |
| |
| case DATALINK_READY: |
| case DATALINK_LCP: |
| case DATALINK_AUTH: |
| case DATALINK_CBCP: |
| case DATALINK_OPEN: |
| return descriptor_IsSet(&dl->chap.desc, fdset) ? 1 : |
| descriptor_IsSet(&dl->physical->desc, fdset); |
| } |
| return 0; |
| } |
| |
| static void |
| datalink_Read(struct fdescriptor *d, struct bundle *bundle, const fd_set *fdset) |
| { |
| struct datalink *dl = descriptor2datalink(d); |
| |
| switch (dl->state) { |
| case DATALINK_CLOSED: |
| case DATALINK_OPENING: |
| break; |
| |
| case DATALINK_HANGUP: |
| case DATALINK_DIAL: |
| case DATALINK_LOGOUT: |
| case DATALINK_LOGIN: |
| descriptor_Read(&dl->chat.desc, bundle, fdset); |
| break; |
| |
| case DATALINK_READY: |
| case DATALINK_LCP: |
| case DATALINK_AUTH: |
| case DATALINK_CBCP: |
| case DATALINK_OPEN: |
| if (descriptor_IsSet(&dl->chap.desc, fdset)) |
| descriptor_Read(&dl->chap.desc, bundle, fdset); |
| if (descriptor_IsSet(&dl->physical->desc, fdset)) |
| descriptor_Read(&dl->physical->desc, bundle, fdset); |
| break; |
| } |
| } |
| |
| static int |
| datalink_Write(struct fdescriptor *d, struct bundle *bundle, |
| const fd_set *fdset) |
| { |
| struct datalink *dl = descriptor2datalink(d); |
| int result = 0; |
| |
| switch (dl->state) { |
| case DATALINK_CLOSED: |
| case DATALINK_OPENING: |
| break; |
| |
| case DATALINK_HANGUP: |
| case DATALINK_DIAL: |
| case DATALINK_LOGOUT: |
| case DATALINK_LOGIN: |
| if ((result = descriptor_Write(&dl->chat.desc, bundle, fdset)) == -1) { |
| datalink_ComeDown(dl, CLOSE_NORMAL); |
| result = 0; |
| } |
| break; |
| |
| case DATALINK_READY: |
| case DATALINK_LCP: |
| case DATALINK_AUTH: |
| case DATALINK_CBCP: |
| case DATALINK_OPEN: |
| if (descriptor_IsSet(&dl->chap.desc, fdset)) |
| switch (descriptor_Write(&dl->chap.desc, bundle, fdset)) { |
| case -1: |
| datalink_ComeDown(dl, CLOSE_NORMAL); |
| break; |
| case 1: |
| result++; |
| } |
| if (descriptor_IsSet(&dl->physical->desc, fdset)) |
| switch (descriptor_Write(&dl->physical->desc, bundle, fdset)) { |
| case -1: |
| datalink_ComeDown(dl, CLOSE_NORMAL); |
| break; |
| case 1: |
| result++; |
| } |
| break; |
| } |
| |
| return result; |
| } |
| |
| void |
| datalink_ComeDown(struct datalink *dl, int how) |
| { |
| int stayonline; |
| |
| if (how == CLOSE_LCP) |
| datalink_DontHangup(dl); |
| else if (how == CLOSE_STAYDOWN) |
| datalink_StayDown(dl); |
| |
| stayonline = dl->stayonline; |
| dl->stayonline = 0; |
| |
| if (dl->state >= DATALINK_READY && stayonline) { |
| physical_StopDeviceTimer(dl->physical); |
| datalink_NewState(dl, DATALINK_READY); |
| } else if (dl->state != DATALINK_CLOSED && dl->state != DATALINK_HANGUP) { |
| physical_Offline(dl->physical); |
| if (dl->script.run && dl->state != DATALINK_OPENING) { |
| if (dl->state == DATALINK_LOGOUT) { |
| datalink_NewState(dl, DATALINK_HANGUP); |
| if (!chat_Setup(&dl->chat, dl->cfg.script.hangup, NULL)) |
| log_Printf(LogWARN, "Invalid hangup script\n"); |
| } else { |
| datalink_NewState(dl, DATALINK_LOGOUT); |
| if (!chat_Setup(&dl->chat, dl->cfg.script.logout, NULL)) |
| log_Printf(LogWARN, "Invalid logout script\n"); |
| } |
| } else |
| datalink_HangupDone(dl); |
| } |
| } |
| |
| static void |
| datalink_LayerStart(void *v, struct fsm *fp) |
| { |
| /* The given FSM is about to start up ! */ |
| struct datalink *dl = (struct datalink *)v; |
| |
| if (fp->proto == PROTO_LCP) |
| (*dl->parent->LayerStart)(dl->parent->object, fp); |
| } |
| |
| static void |
| datalink_LayerUp(void *v, struct fsm *fp) |
| { |
| /* The given fsm is now up */ |
| struct datalink *dl = (struct datalink *)v; |
| struct lcp *lcp = &dl->physical->link.lcp; |
| |
| if (fp->proto == PROTO_LCP) { |
| datalink_GotAuthname(dl, ""); |
| lcp->auth_ineed = lcp->want_auth; |
| lcp->auth_iwait = lcp->his_auth; |
| if (lcp->his_auth || lcp->want_auth) { |
| if (bundle_Phase(dl->bundle) != PHASE_NETWORK) |
| bundle_NewPhase(dl->bundle, PHASE_AUTHENTICATE); |
| log_Printf(LogPHASE, "%s: his = %s, mine = %s\n", dl->name, |
| Auth2Nam(lcp->his_auth, lcp->his_authtype), |
| Auth2Nam(lcp->want_auth, lcp->want_authtype)); |
| if (lcp->his_auth == PROTO_PAP) |
| auth_StartReq(&dl->pap); |
| if (lcp->want_auth == PROTO_CHAP) |
| auth_StartReq(&dl->chap.auth); |
| } else |
| datalink_AuthOk(dl); |
| } else if (fp->proto == PROTO_CCP) |
| (*dl->parent->LayerUp)(dl->parent->object, &dl->physical->link.ccp.fsm); |
| } |
| |
| static void |
| datalink_AuthReInit(struct datalink *dl) |
| { |
| auth_StopTimer(&dl->pap); |
| auth_StopTimer(&dl->chap.auth); |
| chap_ReInit(&dl->chap); |
| } |
| |
| void |
| datalink_GotAuthname(struct datalink *dl, const char *name) |
| { |
| strncpy(dl->peer.authname, name, sizeof dl->peer.authname - 1); |
| dl->peer.authname[sizeof dl->peer.authname - 1] = '\0'; |
| } |
| |
| void |
| datalink_NCPUp(struct datalink *dl) |
| { |
| int ccpok = ccp_SetOpenMode(&dl->physical->link.ccp); |
| |
| if (dl->physical->link.lcp.want_mrru && dl->physical->link.lcp.his_mrru) { |
| /* we've authenticated in multilink mode ! */ |
| switch (mp_Up(&dl->bundle->ncp.mp, dl)) { |
| case MP_LINKSENT: |
| /* We've handed the link off to another ppp (well, we will soon) ! */ |
| return; |
| case MP_UP: |
| /* First link in the bundle */ |
| auth_Select(dl->bundle, dl->peer.authname); |
| bundle_CalculateBandwidth(dl->bundle); |
| /* FALLTHROUGH */ |
| case MP_ADDED: |
| /* We're in multilink mode ! */ |
| dl->physical->link.ccp.fsm.open_mode = OPEN_PASSIVE; /* override */ |
| bundle_CalculateBandwidth(dl->bundle); |
| break; |
| case MP_FAILED: |
| datalink_AuthNotOk(dl); |
| return; |
| } |
| } else if (bundle_Phase(dl->bundle) == PHASE_NETWORK) { |
| log_Printf(LogPHASE, "%s: Already in NETWORK phase\n", dl->name); |
| datalink_NewState(dl, DATALINK_OPEN); |
| bundle_CalculateBandwidth(dl->bundle); |
| (*dl->parent->LayerUp)(dl->parent->object, &dl->physical->link.lcp.fsm); |
| return; |
| } else { |
| dl->bundle->ncp.mp.peer = dl->peer; |
| ncp_SetLink(&dl->bundle->ncp, &dl->physical->link); |
| auth_Select(dl->bundle, dl->peer.authname); |
| } |
| |
| if (ccpok) { |
| fsm_Up(&dl->physical->link.ccp.fsm); |
| fsm_Open(&dl->physical->link.ccp.fsm); |
| } |
| datalink_NewState(dl, DATALINK_OPEN); |
| bundle_NewPhase(dl->bundle, PHASE_NETWORK); |
| (*dl->parent->LayerUp)(dl->parent->object, &dl->physical->link.lcp.fsm); |
| } |
| |
| void |
| datalink_CBCPComplete(struct datalink *dl) |
| { |
| datalink_NewState(dl, DATALINK_LCP); |
| datalink_AuthReInit(dl); |
| fsm_Close(&dl->physical->link.lcp.fsm); |
| } |
| |
| void |
| datalink_CBCPFailed(struct datalink *dl) |
| { |
| cbcp_Down(&dl->cbcp); |
| datalink_CBCPComplete(dl); |
| } |
| |
| void |
| datalink_AuthOk(struct datalink *dl) |
| { |
| if ((dl->physical->link.lcp.his_callback.opmask & |
| CALLBACK_BIT(CALLBACK_CBCP) || |
| dl->physical->link.lcp.want_callback.opmask & |
| CALLBACK_BIT(CALLBACK_CBCP)) && |
| !(dl->physical->link.lcp.want_callback.opmask & |
| CALLBACK_BIT(CALLBACK_AUTH))) { |
| /* We must have agreed CBCP if AUTH isn't there any more */ |
| datalink_NewState(dl, DATALINK_CBCP); |
| cbcp_Up(&dl->cbcp); |
| } else if (dl->physical->link.lcp.want_callback.opmask) { |
| /* It's not CBCP */ |
| log_Printf(LogPHASE, "%s: Shutdown and await peer callback\n", dl->name); |
| datalink_NewState(dl, DATALINK_LCP); |
| datalink_AuthReInit(dl); |
| fsm_Close(&dl->physical->link.lcp.fsm); |
| } else |
| switch (dl->physical->link.lcp.his_callback.opmask) { |
| case 0: |
| datalink_NCPUp(dl); |
| break; |
| |
| case CALLBACK_BIT(CALLBACK_AUTH): |
| auth_SetPhoneList(dl->peer.authname, dl->cbcp.fsm.phone, |
| sizeof dl->cbcp.fsm.phone); |
| if (*dl->cbcp.fsm.phone == '\0' || !strcmp(dl->cbcp.fsm.phone, "*")) { |
| log_Printf(LogPHASE, "%s: %s cannot be called back\n", dl->name, |
| dl->peer.authname); |
| *dl->cbcp.fsm.phone = '\0'; |
| } else { |
| char *ptr = strchr(dl->cbcp.fsm.phone, ','); |
| if (ptr) |
| *ptr = '\0'; /* Call back on the first number */ |
| log_Printf(LogPHASE, "%s: Calling peer back on %s\n", dl->name, |
| dl->cbcp.fsm.phone); |
| dl->cbcp.required = 1; |
| } |
| dl->cbcp.fsm.delay = 0; |
| datalink_NewState(dl, DATALINK_LCP); |
| datalink_AuthReInit(dl); |
| fsm_Close(&dl->physical->link.lcp.fsm); |
| break; |
| |
| case CALLBACK_BIT(CALLBACK_E164): |
| strncpy(dl->cbcp.fsm.phone, dl->physical->link.lcp.his_callback.msg, |
| sizeof dl->cbcp.fsm.phone - 1); |
| dl->cbcp.fsm.phone[sizeof dl->cbcp.fsm.phone - 1] = '\0'; |
| log_Printf(LogPHASE, "%s: Calling peer back on %s\n", dl->name, |
| dl->cbcp.fsm.phone); |
| dl->cbcp.required = 1; |
| dl->cbcp.fsm.delay = 0; |
| datalink_NewState(dl, DATALINK_LCP); |
| datalink_AuthReInit(dl); |
| fsm_Close(&dl->physical->link.lcp.fsm); |
| break; |
| |
| default: |
| log_Printf(LogPHASE, "%s: Oops - Should have NAK'd peer callback !\n", |
| dl->name); |
| datalink_NewState(dl, DATALINK_LCP); |
| datalink_AuthReInit(dl); |
| fsm_Close(&dl->physical->link.lcp.fsm); |
| break; |
| } |
| } |
| |
| void |
| datalink_AuthNotOk(struct datalink *dl) |
| { |
| datalink_NewState(dl, DATALINK_LCP); |
| datalink_AuthReInit(dl); |
| fsm_Close(&dl->physical->link.lcp.fsm); |
| } |
| |
| static void |
| datalink_LayerDown(void *v, struct fsm *fp) |
| { |
| /* The given FSM has been told to come down */ |
| struct datalink *dl = (struct datalink *)v; |
| |
| if (fp->proto == PROTO_LCP) { |
| switch (dl->state) { |
| case DATALINK_OPEN: |
| peerid_Init(&dl->peer); |
| fsm2initial(&dl->physical->link.ccp.fsm); |
| datalink_NewState(dl, DATALINK_LCP); /* before parent TLD */ |
| (*dl->parent->LayerDown)(dl->parent->object, fp); |
| /* FALLTHROUGH (just in case) */ |
| |
| case DATALINK_CBCP: |
| if (!dl->cbcp.required) |
| cbcp_Down(&dl->cbcp); |
| /* FALLTHROUGH (just in case) */ |
| |
| case DATALINK_AUTH: |
| timer_Stop(&dl->pap.authtimer); |
| timer_Stop(&dl->chap.auth.authtimer); |
| } |
| datalink_NewState(dl, DATALINK_LCP); |
| datalink_AuthReInit(dl); |
| } |
| } |
| |
| static void |
| datalink_LayerFinish(void *v, struct fsm *fp) |
| { |
| /* The given fsm is now down */ |
| struct datalink *dl = (struct datalink *)v; |
| |
| if (fp->proto == PROTO_LCP) { |
| fsm2initial(fp); |
| (*dl->parent->LayerFinish)(dl->parent->object, fp); |
| datalink_ComeDown(dl, CLOSE_NORMAL); |
| } else if (fp->state == ST_CLOSED && fp->open_mode == OPEN_PASSIVE) |
| fsm_Open(fp); /* CCP goes to ST_STOPPED */ |
| } |
| |
| struct datalink * |
| datalink_Create(const char *name, struct bundle *bundle, int type) |
| { |
| struct datalink *dl; |
| |
| dl = (struct datalink *)malloc(sizeof(struct datalink)); |
| if (dl == NULL) |
| return dl; |
| |
| dl->desc.type = DATALINK_DESCRIPTOR; |
| dl->desc.UpdateSet = datalink_UpdateSet; |
| dl->desc.IsSet = datalink_IsSet; |
| dl->desc.Read = datalink_Read; |
| dl->desc.Write = datalink_Write; |
| |
| dl->state = DATALINK_CLOSED; |
| |
| *dl->cfg.script.dial = '\0'; |
| *dl->cfg.script.login = '\0'; |
| *dl->cfg.script.logout = '\0'; |
| *dl->cfg.script.hangup = '\0'; |
| *dl->cfg.phone.list = '\0'; |
| *dl->phone.list = '\0'; |
| dl->phone.next = NULL; |
| dl->phone.alt = NULL; |
| dl->phone.chosen = "N/A"; |
| dl->stayonline = 0; |
| dl->script.run = 1; |
| dl->script.packetmode = 1; |
| mp_linkInit(&dl->mp); |
| |
| dl->bundle = bundle; |
| dl->next = NULL; |
| |
| memset(&dl->dial.timer, '\0', sizeof dl->dial.timer); |
| |
| dl->dial.tries = 0; |
| dl->cfg.dial.max = 1; |
| dl->cfg.dial.next_timeout = DIAL_NEXT_TIMEOUT; |
| dl->cfg.dial.timeout = DIAL_TIMEOUT; |
| dl->cfg.dial.inc = 0; |
| dl->cfg.dial.maxinc = 10; |
| |
| dl->reconnect_tries = 0; |
| dl->cfg.reconnect.max = 0; |
| dl->cfg.reconnect.timeout = RECONNECT_TIMEOUT; |
| |
| dl->cfg.callback.opmask = 0; |
| dl->cfg.cbcp.delay = 0; |
| *dl->cfg.cbcp.phone = '\0'; |
| dl->cfg.cbcp.fsmretry = DEF_FSMRETRY; |
| |
| dl->name = strdup(name); |
| peerid_Init(&dl->peer); |
| dl->parent = &bundle->fsm; |
| dl->fsmp.LayerStart = datalink_LayerStart; |
| dl->fsmp.LayerUp = datalink_LayerUp; |
| dl->fsmp.LayerDown = datalink_LayerDown; |
| dl->fsmp.LayerFinish = datalink_LayerFinish; |
| dl->fsmp.object = dl; |
| |
| if ((dl->physical = physical_Create(dl, type)) == NULL) { |
| free(dl->name); |
| free(dl); |
| return NULL; |
| } |
| |
| pap_Init(&dl->pap, dl->physical); |
| chap_Init(&dl->chap, dl->physical); |
| cbcp_Init(&dl->cbcp, dl->physical); |
| |
| memset(&dl->chat, '\0', sizeof dl->chat); /* Force buf{start,end} reset */ |
| chat_Init(&dl->chat, dl->physical); |
| |
| log_Printf(LogPHASE, "%s: Created in %s state\n", |
| dl->name, datalink_State(dl)); |
| |
| return dl; |
| } |
| |
| struct datalink * |
| datalink_Clone(struct datalink *odl, const char *name) |
| { |
| struct datalink *dl; |
| |
| dl = (struct datalink *)malloc(sizeof(struct datalink)); |
| if (dl == NULL) |
| return dl; |
| |
| dl->desc.type = DATALINK_DESCRIPTOR; |
| dl->desc.UpdateSet = datalink_UpdateSet; |
| dl->desc.IsSet = datalink_IsSet; |
| dl->desc.Read = datalink_Read; |
| dl->desc.Write = datalink_Write; |
| |
| dl->state = DATALINK_CLOSED; |
| |
| memcpy(&dl->cfg, &odl->cfg, sizeof dl->cfg); |
| mp_linkInit(&dl->mp); |
| *dl->phone.list = '\0'; |
| dl->phone.next = NULL; |
| dl->phone.alt = NULL; |
| dl->phone.chosen = "N/A"; |
| dl->bundle = odl->bundle; |
| dl->next = NULL; |
| memset(&dl->dial.timer, '\0', sizeof dl->dial.timer); |
| dl->dial.tries = 0; |
| dl->reconnect_tries = 0; |
| dl->name = strdup(name); |
| peerid_Init(&dl->peer); |
| dl->parent = odl->parent; |
| memcpy(&dl->fsmp, &odl->fsmp, sizeof dl->fsmp); |
| dl->fsmp.object = dl; |
| |
| if ((dl->physical = physical_Create(dl, PHYS_INTERACTIVE)) == NULL) { |
| free(dl->name); |
| free(dl); |
| return NULL; |
| } |
| pap_Init(&dl->pap, dl->physical); |
| dl->pap.cfg = odl->pap.cfg; |
| |
| chap_Init(&dl->chap, dl->physical); |
| dl->chap.auth.cfg = odl->chap.auth.cfg; |
| |
| memcpy(&dl->physical->cfg, &odl->physical->cfg, sizeof dl->physical->cfg); |
| memcpy(&dl->physical->link.lcp.cfg, &odl->physical->link.lcp.cfg, |
| sizeof dl->physical->link.lcp.cfg); |
| memcpy(&dl->physical->link.ccp.cfg, &odl->physical->link.ccp.cfg, |
| sizeof dl->physical->link.ccp.cfg); |
| memcpy(&dl->physical->async.cfg, &odl->physical->async.cfg, |
| sizeof dl->physical->async.cfg); |
| |
| cbcp_Init(&dl->cbcp, dl->physical); |
| |
| memset(&dl->chat, '\0', sizeof dl->chat); /* Force buf{start,end} reset */ |
| chat_Init(&dl->chat, dl->physical); |
| |
| log_Printf(LogPHASE, "%s: Cloned in %s state\n", |
| dl->name, datalink_State(dl)); |
| |
| return dl; |
| } |
| |
| struct datalink * |
| datalink_Destroy(struct datalink *dl) |
| { |
| struct datalink *result; |
| |
| if (dl->state != DATALINK_CLOSED) { |
| log_Printf(LogERROR, "Oops, destroying a datalink in state %s\n", |
| datalink_State(dl)); |
| switch (dl->state) { |
| case DATALINK_HANGUP: |
| case DATALINK_DIAL: |
| case DATALINK_LOGIN: |
| chat_Finish(&dl->chat); /* Gotta blat the timers ! */ |
| break; |
| } |
| } |
| |
| chat_Destroy(&dl->chat); |
| timer_Stop(&dl->dial.timer); |
| result = dl->next; |
| physical_Destroy(dl->physical); |
| free(dl->name); |
| free(dl); |
| |
| return result; |
| } |
| |
| void |
| datalink_Up(struct datalink *dl, int runscripts, int packetmode) |
| { |
| if (!Enabled(dl->bundle, OPT_FORCE_SCRIPTS) && |
| (dl->physical->type & (PHYS_DIRECT|PHYS_DEDICATED))) |
| /* Ignore scripts */ |
| runscripts = 0; |
| |
| switch (dl->state) { |
| case DATALINK_CLOSED: |
| if (bundle_Phase(dl->bundle) == PHASE_DEAD || |
| bundle_Phase(dl->bundle) == PHASE_TERMINATE) |
| bundle_NewPhase(dl->bundle, PHASE_ESTABLISH); |
| datalink_NewState(dl, DATALINK_OPENING); |
| dl->reconnect_tries = |
| dl->physical->type == PHYS_DIRECT ? 0 : dl->cfg.reconnect.max; |
| dl->dial.tries = dl->cfg.dial.max; |
| dl->script.run = runscripts; |
| dl->script.packetmode = packetmode; |
| break; |
| |
| case DATALINK_OPENING: |
| if (!dl->script.run && runscripts) |
| dl->script.run = 1; |
| /* FALLTHROUGH */ |
| |
| case DATALINK_DIAL: |
| case DATALINK_LOGIN: |
| case DATALINK_READY: |
| if (!dl->script.packetmode && packetmode) { |
| dl->script.packetmode = 1; |
| if (dl->state == DATALINK_READY) { |
| dl->script.run = 0; |
| datalink_NewState(dl, DATALINK_CARRIER); |
| } |
| } |
| break; |
| } |
| } |
| |
| void |
| datalink_Close(struct datalink *dl, int how) |
| { |
| /* Please close */ |
| switch (dl->state) { |
| case DATALINK_OPEN: |
| peerid_Init(&dl->peer); |
| fsm2initial(&dl->physical->link.ccp.fsm); |
| /* FALLTHROUGH */ |
| |
| case DATALINK_CBCP: |
| case DATALINK_AUTH: |
| case DATALINK_LCP: |
| datalink_AuthReInit(dl); |
| if (how == CLOSE_LCP) |
| datalink_DontHangup(dl); |
| else if (how == CLOSE_STAYDOWN) |
| datalink_StayDown(dl); |
| fsm_Close(&dl->physical->link.lcp.fsm); |
| break; |
| |
| default: |
| datalink_ComeDown(dl, how); |
| } |
| } |
| |
| void |
| datalink_Down(struct datalink *dl, int how) |
| { |
| /* Carrier is lost */ |
| switch (dl->state) { |
| case DATALINK_OPEN: |
| peerid_Init(&dl->peer); |
| fsm2initial(&dl->physical->link.ccp.fsm); |
| /* FALLTHROUGH */ |
| |
| case DATALINK_CBCP: |
| case DATALINK_AUTH: |
| case DATALINK_LCP: |
| fsm2initial(&dl->physical->link.lcp.fsm); |
| if (dl->state == DATALINK_OPENING) |
| return; /* we're doing a callback... */ |
| /* FALLTHROUGH */ |
| |
| default: |
| datalink_ComeDown(dl, how); |
| } |
| } |
| |
| void |
| datalink_StayDown(struct datalink *dl) |
| { |
| dl->dial.tries = -1; |
| dl->reconnect_tries = 0; |
| dl->stayonline = 0; |
| } |
| |
| void |
| datalink_DontHangup(struct datalink *dl) |
| { |
| dl->dial.tries = -1; |
| dl->reconnect_tries = 0; |
| dl->stayonline = dl->state >= DATALINK_LCP ? 1 : 0; |
| } |
| |
| int |
| datalink_Show(struct cmdargs const *arg) |
| { |
| prompt_Printf(arg->prompt, "Name: %s\n", arg->cx->name); |
| prompt_Printf(arg->prompt, " State: %s\n", |
| datalink_State(arg->cx)); |
| prompt_Printf(arg->prompt, " Peer name: "); |
| if (*arg->cx->peer.authname) |
| prompt_Printf(arg->prompt, "%s\n", arg->cx->peer.authname); |
| else if (arg->cx->state == DATALINK_OPEN) |
| prompt_Printf(arg->prompt, "None requested\n"); |
| else |
| prompt_Printf(arg->prompt, "N/A\n"); |
| prompt_Printf(arg->prompt, " Discriminator: %s\n", |
| mp_Enddisc(arg->cx->peer.enddisc.class, |
| arg->cx->peer.enddisc.address, |
| arg->cx->peer.enddisc.len)); |
| |
| prompt_Printf(arg->prompt, "\nDefaults:\n"); |
| prompt_Printf(arg->prompt, " Phone List: %s\n", |
| arg->cx->cfg.phone.list); |
| if (arg->cx->cfg.dial.max) |
| prompt_Printf(arg->prompt, " Dial tries: %d, delay ", |
| arg->cx->cfg.dial.max); |
| else |
| prompt_Printf(arg->prompt, " Dial tries: infinite, delay "); |
| if (arg->cx->cfg.dial.next_timeout >= 0) |
| prompt_Printf(arg->prompt, "%ds/", arg->cx->cfg.dial.next_timeout); |
| else |
| prompt_Printf(arg->prompt, "random/"); |
| if (arg->cx->cfg.dial.timeout >= 0) |
| prompt_Printf(arg->prompt, "%ds\n", arg->cx->cfg.dial.timeout); |
| else |
| prompt_Printf(arg->prompt, "random\n"); |
| prompt_Printf(arg->prompt, " Reconnect tries: %d, delay ", |
| arg->cx->cfg.reconnect.max); |
| if (arg->cx->cfg.reconnect.timeout > 0) |
| prompt_Printf(arg->prompt, "%ds\n", arg->cx->cfg.reconnect.timeout); |
| else |
| prompt_Printf(arg->prompt, "random\n"); |
| prompt_Printf(arg->prompt, " Callback %s ", arg->cx->physical->type == |
| PHYS_DIRECT ? "accepted: " : "requested:"); |
| if (!arg->cx->cfg.callback.opmask) |
| prompt_Printf(arg->prompt, "none\n"); |
| else { |
| int comma = 0; |
| |
| if (arg->cx->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_NONE)) { |
| prompt_Printf(arg->prompt, "none"); |
| comma = 1; |
| } |
| if (arg->cx->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_AUTH)) { |
| prompt_Printf(arg->prompt, "%sauth", comma ? ", " : ""); |
| comma = 1; |
| } |
| if (arg->cx->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_E164)) { |
| prompt_Printf(arg->prompt, "%sE.164", comma ? ", " : ""); |
| if (arg->cx->physical->type != PHYS_DIRECT) |
| prompt_Printf(arg->prompt, " (%s)", arg->cx->cfg.callback.msg); |
| comma = 1; |
| } |
| if (arg->cx->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_CBCP)) { |
| prompt_Printf(arg->prompt, "%scbcp\n", comma ? ", " : ""); |
| prompt_Printf(arg->prompt, " CBCP: delay: %ds\n", |
| arg->cx->cfg.cbcp.delay); |
| prompt_Printf(arg->prompt, " phone: "); |
| if (!strcmp(arg->cx->cfg.cbcp.phone, "*")) { |
| if (arg->cx->physical->type & PHYS_DIRECT) |
| prompt_Printf(arg->prompt, "Caller decides\n"); |
| else |
| prompt_Printf(arg->prompt, "Dialback server decides\n"); |
| } else |
| prompt_Printf(arg->prompt, "%s\n", arg->cx->cfg.cbcp.phone); |
| prompt_Printf(arg->prompt, " timeout: %lds\n", |
| arg->cx->cfg.cbcp.fsmretry); |
| } else |
| prompt_Printf(arg->prompt, "\n"); |
| } |
| |
| prompt_Printf(arg->prompt, " Dial Script: %s\n", |
| arg->cx->cfg.script.dial); |
| prompt_Printf(arg->prompt, " Login Script: %s\n", |
| arg->cx->cfg.script.login); |
| prompt_Printf(arg->prompt, " Logout Script: %s\n", |
| arg->cx->cfg.script.logout); |
| prompt_Printf(arg->prompt, " Hangup Script: %s\n", |
| arg->cx->cfg.script.hangup); |
| return 0; |
| } |
| |
| int |
| datalink_SetReconnect(struct cmdargs const *arg) |
| { |
| if (arg->argc == arg->argn+2) { |
| arg->cx->cfg.reconnect.timeout = atoi(arg->argv[arg->argn]); |
| arg->cx->cfg.reconnect.max = atoi(arg->argv[arg->argn+1]); |
| return 0; |
| } |
| return -1; |
| } |
| |
| int |
| datalink_SetRedial(struct cmdargs const *arg) |
| { |
| const char *sep, *osep; |
| int timeout, inc, maxinc, tries; |
| |
| if (arg->argc == arg->argn+1 || arg->argc == arg->argn+2) { |
| if (strncasecmp(arg->argv[arg->argn], "random", 6) == 0 && |
| (arg->argv[arg->argn][6] == '\0' || arg->argv[arg->argn][6] == '.')) { |
| arg->cx->cfg.dial.timeout = -1; |
| randinit(); |
| } else { |
| timeout = atoi(arg->argv[arg->argn]); |
| |
| if (timeout >= 0) |
| arg->cx->cfg.dial.timeout = timeout; |
| else { |
| log_Printf(LogWARN, "Invalid redial timeout\n"); |
| return -1; |
| } |
| } |
| |
| sep = strchr(arg->argv[arg->argn], '+'); |
| if (sep) { |
| inc = atoi(++sep); |
| osep = sep; |
| if (inc >= 0) |
| arg->cx->cfg.dial.inc = inc; |
| else { |
| log_Printf(LogWARN, "Invalid timeout increment\n"); |
| return -1; |
| } |
| sep = strchr(sep, '-'); |
| if (sep) { |
| maxinc = atoi(++sep); |
| if (maxinc >= 0) |
| arg->cx->cfg.dial.maxinc = maxinc; |
| else { |
| log_Printf(LogWARN, "Invalid maximum timeout increments\n"); |
| return -1; |
| } |
| } else { |
| /* Default timeout increment */ |
| arg->cx->cfg.dial.maxinc = 10; |
| sep = osep; |
| } |
| } else { |
| /* Default timeout increment & max increment */ |
| arg->cx->cfg.dial.inc = 0; |
| arg->cx->cfg.dial.maxinc = 10; |
| sep = arg->argv[arg->argn]; |
| } |
| |
| sep = strchr(sep, '.'); |
| if (sep) { |
| if (strcasecmp(++sep, "random") == 0) { |
| arg->cx->cfg.dial.next_timeout = -1; |
| randinit(); |
| } else { |
| timeout = atoi(sep); |
| if (timeout >= 0) |
| arg->cx->cfg.dial.next_timeout = timeout; |
| else { |
| log_Printf(LogWARN, "Invalid next redial timeout\n"); |
| return -1; |
| } |
| } |
| } else |
| /* Default next timeout */ |
| arg->cx->cfg.dial.next_timeout = DIAL_NEXT_TIMEOUT; |
| |
| if (arg->argc == arg->argn+2) { |
| tries = atoi(arg->argv[arg->argn+1]); |
| |
| if (tries >= 0) { |
| arg->cx->cfg.dial.max = tries; |
| } else { |
| log_Printf(LogWARN, "Invalid retry value\n"); |
| return 1; |
| } |
| } |
| return 0; |
| } |
| |
| return -1; |
| } |
| |
| static const char * const states[] = { |
| "closed", |
| "opening", |
| "hangup", |
| "dial", |
| "carrier", |
| "logout", |
| "login", |
| "ready", |
| "lcp", |
| "auth", |
| "cbcp", |
| "open" |
| }; |
| |
| const char * |
| datalink_State(struct datalink *dl) |
| { |
| if (dl->state >= sizeof states / sizeof states[0]) |
| return "unknown"; |
| return states[dl->state]; |
| } |
| |
| static void |
| datalink_NewState(struct datalink *dl, unsigned state) |
| { |
| if (state != dl->state) { |
| if (state < sizeof states / sizeof states[0]) { |
| log_Printf(LogPHASE, "%s: %s -> %s\n", dl->name, datalink_State(dl), |
| states[state]); |
| dl->state = state; |
| } else |
| log_Printf(LogERROR, "%s: Can't enter state %d !\n", dl->name, state); |
| } |
| } |
| |
| struct datalink * |
| iov2datalink(struct bundle *bundle, struct iovec *iov, int *niov, int maxiov, |
| int fd, int *auxfd, int *nauxfd) |
| { |
| struct datalink *dl, *cdl; |
| struct fsm_retry copy; |
| char *oname, *pname; |
| |
| dl = (struct datalink *)iov[(*niov)++].iov_base; |
| dl->name = iov[*niov].iov_base; |
| |
| if (dl->name[DATALINK_MAXNAME-1]) { |
| dl->name[DATALINK_MAXNAME-1] = '\0'; |
| if (strlen(dl->name) == DATALINK_MAXNAME - 1) |
| log_Printf(LogWARN, "Datalink name truncated to \"%s\"\n", dl->name); |
| } |
| |
| /* Make sure the name is unique ! */ |
| oname = NULL; |
| do { |
| for (cdl = bundle->links; cdl; cdl = cdl->next) |
| if (!strcasecmp(dl->name, cdl->name)) { |
| if ((pname = datalink_NextName(dl)) == NULL) { |
| for ((*niov)--; *niov < maxiov; (*niov)++) |
| free(iov[*niov].iov_base); |
| return NULL; |
| } else if (oname) |
| free(pname); |
| else |
| oname = pname; |
| break; /* Keep renaming 'till we have no conflicts */ |
| } |
| } while (cdl); |
| |
| if (oname) { |
| log_Printf(LogPHASE, "Rename link %s to %s\n", oname, dl->name); |
| free(oname); |
| } else { |
| dl->name = strdup(dl->name); |
| free(iov[*niov].iov_base); |
| } |
| (*niov)++; |
| |
| dl->desc.type = DATALINK_DESCRIPTOR; |
| dl->desc.UpdateSet = datalink_UpdateSet; |
| dl->desc.IsSet = datalink_IsSet; |
| dl->desc.Read = datalink_Read; |
| dl->desc.Write = datalink_Write; |
| |
| mp_linkInit(&dl->mp); |
| *dl->phone.list = '\0'; |
| dl->phone.next = NULL; |
| dl->phone.alt = NULL; |
| dl->phone.chosen = "N/A"; |
| |
| dl->bundle = bundle; |
| dl->next = NULL; |
| memset(&dl->dial.timer, '\0', sizeof dl->dial.timer); |
| dl->dial.tries = 0; |
| dl->reconnect_tries = 0; |
| dl->parent = &bundle->fsm; |
| dl->fsmp.LayerStart = datalink_LayerStart; |
| dl->fsmp.LayerUp = datalink_LayerUp; |
| dl->fsmp.LayerDown = datalink_LayerDown; |
| dl->fsmp.LayerFinish = datalink_LayerFinish; |
| dl->fsmp.object = dl; |
| |
| dl->physical = iov2physical(dl, iov, niov, maxiov, fd, auxfd, nauxfd); |
| |
| if (!dl->physical) { |
| free(dl->name); |
| free(dl); |
| dl = NULL; |
| } else { |
| copy = dl->pap.cfg.fsm; |
| pap_Init(&dl->pap, dl->physical); |
| dl->pap.cfg.fsm = copy; |
| |
| copy = dl->chap.auth.cfg.fsm; |
| chap_Init(&dl->chap, dl->physical); |
| dl->chap.auth.cfg.fsm = copy; |
| |
| cbcp_Init(&dl->cbcp, dl->physical); |
| |
| memset(&dl->chat, '\0', sizeof dl->chat); /* Force buf{start,end} reset */ |
| chat_Init(&dl->chat, dl->physical); |
| |
| log_Printf(LogPHASE, "%s: Transferred in %s state\n", |
| dl->name, datalink_State(dl)); |
| } |
| |
| return dl; |
| } |
| |
| int |
| datalink2iov(struct datalink *dl, struct iovec *iov, int *niov, int maxiov, |
| int *auxfd, int *nauxfd) |
| { |
| /* If `dl' is NULL, we're allocating before a Fromiov() */ |
| int link_fd; |
| |
| if (dl) { |
| timer_Stop(&dl->dial.timer); |
| /* The following is purely for the sake of paranoia */ |
| cbcp_Down(&dl->cbcp); |
| timer_Stop(&dl->pap.authtimer); |
| timer_Stop(&dl->chap.auth.authtimer); |
| } |
| |
| if (*niov >= maxiov - 1) { |
| log_Printf(LogERROR, "Toiov: No room for datalink !\n"); |
| if (dl) { |
| free(dl->name); |
| free(dl); |
| } |
| return -1; |
| } |
| |
| iov[*niov].iov_base = (void *)dl; |
| iov[(*niov)++].iov_len = sizeof *dl; |
| iov[*niov].iov_base = dl ? realloc(dl->name, DATALINK_MAXNAME) : NULL; |
| iov[(*niov)++].iov_len = DATALINK_MAXNAME; |
| |
| link_fd = physical2iov(dl ? dl->physical : NULL, iov, niov, maxiov, auxfd, |
| nauxfd); |
| |
| if (link_fd == -1 && dl) { |
| free(dl->name); |
| free(dl); |
| } |
| |
| return link_fd; |
| } |
| |
| void |
| datalink_Rename(struct datalink *dl, const char *name) |
| { |
| free(dl->name); |
| dl->physical->link.name = dl->name = strdup(name); |
| } |
| |
| static char * |
| datalink_NextName(struct datalink *dl) |
| { |
| int f, n; |
| char *name, *oname; |
| |
| n = strlen(dl->name); |
| if ((name = (char *)malloc(n+3)) == NULL) { |
| log_Printf(LogERROR, "datalink_NextName: Out of memory !\n"); |
| return NULL; |
| } |
| for (f = n - 1; f >= 0; f--) |
| if (!isdigit(dl->name[f])) |
| break; |
| n = sprintf(name, "%.*s-", dl->name[f] == '-' ? f : f + 1, dl->name); |
| sprintf(name + n, "%d", atoi(dl->name + f + 1) + 1); |
| oname = dl->name; |
| dl->name = name; |
| /* our physical link name isn't updated (it probably isn't created yet) */ |
| return oname; |
| } |
| |
| int |
| datalink_SetMode(struct datalink *dl, int mode) |
| { |
| if (!physical_SetMode(dl->physical, mode)) |
| return 0; |
| if (dl->physical->type & (PHYS_DIRECT|PHYS_DEDICATED)) |
| dl->script.run = 0; |
| if (dl->physical->type == PHYS_DIRECT) |
| dl->reconnect_tries = 0; |
| if (mode & (PHYS_DDIAL|PHYS_BACKGROUND|PHYS_FOREGROUND) && |
| dl->state <= DATALINK_READY) |
| datalink_Up(dl, 1, 1); |
| return 1; |
| } |
| |
| int |
| datalink_GetDialTimeout(struct datalink *dl) |
| { |
| int result = dl->cfg.dial.timeout + dl->dial.incs * dl->cfg.dial.inc; |
| |
| if (dl->dial.incs < dl->cfg.dial.maxinc) |
| dl->dial.incs++; |
| |
| return result; |
| } |