/* * main.c - Point-to-Point Protocol main module * * Copyright (c) 1989 Carnegie Mellon University. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that the above copyright notice and this paragraph are * duplicated in all such forms and that any documentation, * advertising materials, and other materials related to such * distribution and use acknowledge that the software was developed * by Carnegie Mellon University. The name of the * University may not be used to endorse or promote products derived * from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #define RCSID "$Id$" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pppd.h" #include "magic.h" #include "fsm.h" #include "lcp.h" #include "ipcp.h" #ifdef INET6 #include "ipv6cp.h" #endif #include "upap.h" #include "chap.h" #include "ccp.h" #include "pathnames.h" #include "patchlevel.h" #include "rtemsdialer.h" #ifdef CBCP_SUPPORT #include "cbcp.h" #endif #ifdef IPX_CHANGE #include "ipxcp.h" #endif /* IPX_CHANGE */ #ifdef AT_CHANGE #include "atcp.h" #endif static const char rcsid[] = RCSID; /* interface vars */ char ifname[32]; /* Interface name */ int pppifunit; /* Interface unit number */ char hostname[MAXNAMELEN]; /* Our hostname */ static char ppp_devnam[MAXPATHLEN]; /* name of PPP tty (maybe ttypx) */ int ttyfd; /* Serial port file descriptor */ int baud_rate; /* Actual bits/second for serial device */ int hungup; /* terminal has been hung up */ int privileged; /* we're running as real uid root */ int need_holdoff; /* need holdoff period before restarting */ int detached; /* have detached from terminal */ struct stat devstat; /* result of stat() on devnam */ int prepass = 0; /* doing prepass to find device name */ int devnam_fixed; /* set while in options.ttyxx file */ volatile int pppd_status; /* exit status for pppd */ int unsuccess; /* # unsuccessful connection attempts */ int do_callback; /* != 0 if we should do callback next */ int doing_callback; /* != 0 if we are doing callback */ char *callback_script; /* script for doing callback */ dialerfp pppd_dialer; int (*holdoff_hook) __P((void)) = NULL; int (*new_phase_hook) __P((int)) = NULL; static int fd_ppp = -1; /* fd for talking PPP */ static int pty_master; /* fd for master side of pty */ static int pty_slave; /* fd for slave side of pty */ static int real_ttyfd; /* fd for actual serial port (not pty) */ int pppd_phase; /* where the link is at */ int pppd_kill_link; int open_ccp_flag; char **script_env; /* Env. variable values for scripts */ int s_env_nalloc; /* # words avail at script_env */ u_char outpacket_buf[PPP_MRU+PPP_HDRLEN]; /* buffer for outgoing packet */ u_char inpacket_buf[PPP_MRU+PPP_HDRLEN]; /* buffer for incoming packet */ char *no_ppp_msg = "Sorry - this system lacks PPP kernel support\n"; static struct timeval start_time; /* Time when link was started. */ struct pppd_stats link_stats; int link_connect_time; int link_stats_valid; /* Prototypes for procedures local to this file. */ static void cleanup __P((void)); static void close_tty __P((void)); static void get_input __P((void)); static void calltimeout __P((void)); static struct timeval *timeleft __P((struct timeval *)); static void holdoff_end __P((void *)); static int device_script __P((int, int, char *)); extern char *ttyname __P((int)); extern char *getlogin __P((void)); int pppdmain __P((int, char *[])); /* * PPP Data Link Layer "protocol" table. * One entry per supported protocol. * The last entry must be NULL. */ struct protent *protocols[] = { &lcp_protent, &pap_protent, &chap_protent, #ifdef CBCP_SUPPORT &cbcp_protent, #endif &ipcp_protent, #ifdef INET6 &ipv6cp_protent, #endif &ccp_protent, #ifdef IPX_CHANGE &ipxcp_protent, #endif #ifdef AT_CHANGE &atcp_protent, #endif NULL }; int pppdmain(argc, argv) int argc; char *argv[]; { int i, fdflags, t; char *connector; struct timeval timo; struct protent *protp; new_phase(PHASE_INITIALIZE); script_env = NULL; hostname[MAXNAMELEN-1] = 0; privileged = 1; privileged_option = 1; /* * Initialize magic number generator now so that protocols may * use magic numbers in initialization. */ magic_init(); #ifdef XXX_XXX /* moved code the the rtems_pppd_reset_options function */ /* * Initialize to the standard option set, then parse, in order, * the system options file, the user's options file, * the tty's options file, and the command line arguments. */ for (i = 0; (protp = protocols[i]) != NULL; ++i) (*protp->init)(0); #endif if (!ppp_available()) { option_error(no_ppp_msg); return(EXIT_NO_KERNEL_SUPPORT); } /* * Check that the options given are valid and consistent. */ if (!sys_check_options()) { return(EXIT_OPTION_ERROR); } if (!auth_check_options()) { return(EXIT_OPTION_ERROR); } for (i = 0; (protp = protocols[i]) != NULL; ++i) if (protp->check_options != NULL) (*protp->check_options)(); /* default holdoff to 0 if no connect script has been given */ if (connect_script == 0 && !holdoff_specified) holdoff = 0; if (default_device) nodetach = 1; /* * Initialize system-dependent stuff. */ sys_init(); /* if (debug) setlogmask(LOG_UPTO(LOG_DEBUG)); */ do_callback = 0; for (;;) { need_holdoff = 1; ttyfd = -1; real_ttyfd = -1; pppd_status = EXIT_OK; ++unsuccess; doing_callback = do_callback; do_callback = 0; new_phase(PHASE_SERIALCONN); /* * Get a pty master/slave pair if the pty, notty, or record * options were specified. */ strlcpy(ppp_devnam, devnam, sizeof(ppp_devnam)); pty_master = -1; pty_slave = -1; /* * Open the serial device and set it up to be the ppp interface. * First we open it in non-blocking mode so we can set the * various termios flags appropriately. If we aren't dialling * out and we want to use the modem lines, we reopen it later * in order to wait for the carrier detect signal from the modem. */ hungup = 0; pppd_kill_link = 0; connector = doing_callback? callback_script: connect_script; if (devnam[0] != 0) { for (;;) { /* If the user specified the device name, become the user before opening it. */ int err; ttyfd = open(devnam, O_NONBLOCK | O_RDWR, 0); err = errno; if (ttyfd >= 0) { break; } errno = err; if (err != EINTR) { error("Failed to open %s: %m", devnam); pppd_status = EXIT_OPEN_FAILED; } if (!persist || err != EINTR) goto fail; } if ((fdflags = fcntl(ttyfd, F_GETFL)) == -1 || fcntl(ttyfd, F_SETFL, fdflags & ~O_NONBLOCK) < 0) warn("Couldn't reset non-blocking mode on device: %m"); /* * Set line speed, flow control, etc. * If we have a non-null connection or initializer script, * on most systems we set CLOCAL for now so that we can talk * to the modem before carrier comes up. But this has the * side effect that we might miss it if CD drops before we * get to clear CLOCAL below. On systems where we can talk * successfully to the modem with CLOCAL clear and CD down, * we could clear CLOCAL at this point. */ set_up_tty(ttyfd, ((connector != NULL && connector[0] != 0) || initializer != NULL)); real_ttyfd = ttyfd; } /* run connection script */ if ((connector && connector[0]) || initializer) { if (real_ttyfd != -1) { /* XXX do this if doing_callback == CALLBACK_DIALIN? */ if (!default_device && modem) { setdtr(real_ttyfd, 0); /* in case modem is off hook */ sleep(1); setdtr(real_ttyfd, 1); } } if (initializer && initializer[0]) { if (device_script(ttyfd, DIALER_INIT, initializer) < 0) { error("Initializer script failed"); pppd_status = EXIT_INIT_FAILED; goto fail; } if (pppd_kill_link) goto disconnect; info("Serial port initialized."); } if (connector && connector[0]) { if (device_script(ttyfd, DIALER_CONNECT, connector) < 0) { error("Connect script failed"); pppd_status = EXIT_CONNECT_FAILED; goto fail; } if (pppd_kill_link) goto disconnect; info("Serial connection established."); } /* set line speed, flow control, etc.; clear CLOCAL if modem option */ if (real_ttyfd != -1) set_up_tty(real_ttyfd, 0); if (doing_callback == CALLBACK_DIALIN) connector = NULL; } /* reopen tty if necessary to wait for carrier */ if (connector == NULL && modem && devnam[0] != 0) { for (;;) { if ((i = open(devnam, O_RDWR)) >= 0) break; if (errno != EINTR) { error("Failed to reopen %s: %m", devnam); pppd_status = EXIT_OPEN_FAILED; } if (!persist || errno != EINTR || hungup || pppd_kill_link) goto fail; } close(i); } info("Serial connection established."); sleep(1); /* run welcome script, if any */ if (welcomer && welcomer[0]) { if (device_script(ttyfd, DIALER_WELCOME, welcomer) < 0) warn("Welcome script failed"); } /* set up the serial device as a ppp interface */ fd_ppp = establish_ppp(ttyfd); if (fd_ppp < 0) { pppd_status = EXIT_FATAL_ERROR; goto disconnect; } if (!demand) { info("Using interface ppp%d", pppifunit); slprintf(ifname, sizeof(ifname), "ppp%d", pppifunit); } /* * Start opening the connection and wait for * incoming events (reply, timeout, etc.). */ notice("Connect: %s <--> %s", ifname, ppp_devnam); gettimeofday(&start_time, NULL); lcp_lowerup(0); lcp_open(0); /* Start protocol */ open_ccp_flag = 0; pppd_status = EXIT_NEGOTIATION_FAILED; new_phase(PHASE_ESTABLISH); while (pppd_phase != PHASE_DEAD) { wait_input(timeleft(&timo)); calltimeout(); get_input(); if (pppd_kill_link) { lcp_close(0, "User request"); pppd_kill_link = 0; } if (open_ccp_flag) { if (pppd_phase == PHASE_NETWORK || pppd_phase == PHASE_RUNNING) { ccp_fsm[0].flags = OPT_RESTART; /* clears OPT_SILENT */ (*ccp_protent.open)(0); } open_ccp_flag = 0; } } /* * If we may want to bring the link up again, transfer * the ppp unit back to the loopback. Set the * real serial device back to its normal mode of operation. */ clean_check(); if (demand) restore_loop(); disestablish_ppp(ttyfd); fd_ppp = -1; if (!hungup) lcp_lowerdown(0); /* * Run disconnector script, if requested. * XXX we may not be able to do this if the line has hung up! */ disconnect: if (disconnect_script && !hungup) { new_phase(PHASE_DISCONNECT); if (real_ttyfd >= 0) set_up_tty(real_ttyfd, 1); if (device_script(ttyfd, DIALER_DISCONNECT, disconnect_script) < 0) { warn("disconnect script failed"); } else { info("Serial link disconnected."); } } fail: if (pty_master >= 0) close(pty_master); if (pty_slave >= 0) close(pty_slave); if (real_ttyfd >= 0) close_tty(); if (!persist || (maxfail > 0 && unsuccess >= maxfail)) break; pppd_kill_link = 0; if (demand) demand_discard(); t = need_holdoff? holdoff: 0; if (holdoff_hook) t = (*holdoff_hook)(); if (t > 0) { new_phase(PHASE_HOLDOFF); TIMEOUT(holdoff_end, NULL, t); do { wait_input(timeleft(&timo)); calltimeout(); if (pppd_kill_link) { pppd_kill_link = 0; new_phase(PHASE_DORMANT); /* allow signal to end holdoff */ } } while (pppd_phase == PHASE_HOLDOFF); if (!persist) break; } } die(pppd_status); return pppd_status; } /* * holdoff_end - called via a timeout when the holdoff period ends. */ static void holdoff_end(arg) void *arg; { new_phase(PHASE_DORMANT); } /* List of protocol names, to make our messages a little more informative. */ struct protocol_list { u_short proto; const char *name; } protocol_list[] = { { 0x21, "IP" }, { 0x23, "OSI Network Layer" }, { 0x25, "Xerox NS IDP" }, { 0x27, "DECnet Phase IV" }, { 0x29, "Appletalk" }, { 0x2b, "Novell IPX" }, { 0x2d, "VJ compressed TCP/IP" }, { 0x2f, "VJ uncompressed TCP/IP" }, { 0x31, "Bridging PDU" }, { 0x33, "Stream Protocol ST-II" }, { 0x35, "Banyan Vines" }, { 0x39, "AppleTalk EDDP" }, { 0x3b, "AppleTalk SmartBuffered" }, { 0x3d, "Multi-Link" }, { 0x3f, "NETBIOS Framing" }, { 0x41, "Cisco Systems" }, { 0x43, "Ascom Timeplex" }, { 0x45, "Fujitsu Link Backup and Load Balancing (LBLB)" }, { 0x47, "DCA Remote Lan" }, { 0x49, "Serial Data Transport Protocol (PPP-SDTP)" }, { 0x4b, "SNA over 802.2" }, { 0x4d, "SNA" }, { 0x4f, "IP6 Header Compression" }, { 0x6f, "Stampede Bridging" }, { 0xfb, "single-link compression" }, { 0xfd, "1st choice compression" }, { 0x0201, "802.1d Hello Packets" }, { 0x0203, "IBM Source Routing BPDU" }, { 0x0205, "DEC LANBridge100 Spanning Tree" }, { 0x0231, "Luxcom" }, { 0x0233, "Sigma Network Systems" }, { 0x8021, "Internet Protocol Control Protocol" }, { 0x8023, "OSI Network Layer Control Protocol" }, { 0x8025, "Xerox NS IDP Control Protocol" }, { 0x8027, "DECnet Phase IV Control Protocol" }, { 0x8029, "Appletalk Control Protocol" }, { 0x802b, "Novell IPX Control Protocol" }, { 0x8031, "Bridging NCP" }, { 0x8033, "Stream Protocol Control Protocol" }, { 0x8035, "Banyan Vines Control Protocol" }, { 0x803d, "Multi-Link Control Protocol" }, { 0x803f, "NETBIOS Framing Control Protocol" }, { 0x8041, "Cisco Systems Control Protocol" }, { 0x8043, "Ascom Timeplex" }, { 0x8045, "Fujitsu LBLB Control Protocol" }, { 0x8047, "DCA Remote Lan Network Control Protocol (RLNCP)" }, { 0x8049, "Serial Data Control Protocol (PPP-SDCP)" }, { 0x804b, "SNA over 802.2 Control Protocol" }, { 0x804d, "SNA Control Protocol" }, { 0x804f, "IP6 Header Compression Control Protocol" }, { 0x006f, "Stampede Bridging Control Protocol" }, { 0x80fb, "Single Link Compression Control Protocol" }, { 0x80fd, "Compression Control Protocol" }, { 0xc021, "Link Control Protocol" }, { 0xc023, "Password Authentication Protocol" }, { 0xc025, "Link Quality Report" }, { 0xc027, "Shiva Password Authentication Protocol" }, { 0xc029, "CallBack Control Protocol (CBCP)" }, { 0xc081, "Container Control Protocol" }, { 0xc223, "Challenge Handshake Authentication Protocol" }, { 0xc281, "Proprietary Authentication Protocol" }, { 0, NULL }, }; /* * protocol_name - find a name for a PPP protocol. */ const char * protocol_name(proto) int proto; { struct protocol_list *lp; for (lp = protocol_list; lp->proto != 0; ++lp) if (proto == lp->proto) return lp->name; return NULL; } /* * get_input - called when incoming data is available. */ static void get_input(void) { int len, i; u_char *p; u_short protocol; struct protent *protp; p = inpacket_buf; /* point to beginning of packet buffer */ len = read_packet(inpacket_buf); if (len < 0) return; if (len == 0) { notice("Modem hangup"); hungup = 1; pppd_status = EXIT_HANGUP; lcp_lowerdown(0); /* serial link is no longer available */ link_terminated(0); return; } if (debug /*&& (debugflags & DBG_INPACKET)*/) dbglog("rcvd %P", p, len); if (len < PPP_HDRLEN) { MAINDEBUG(("io(): Received short packet.")); return; } p += 2; /* Skip address and control */ GETSHORT(protocol, p); len -= PPP_HDRLEN; /* * Toss all non-LCP packets unless LCP is OPEN. */ if (protocol != PPP_LCP && lcp_fsm[0].state != OPENED) { MAINDEBUG(("get_input: Received non-LCP packet when LCP not open.")); return; } /* * Until we get past the authentication phase, toss all packets * except LCP, LQR and authentication packets. */ if (pppd_phase <= PHASE_AUTHENTICATE && !(protocol == PPP_LCP || protocol == PPP_LQR || protocol == PPP_PAP || protocol == PPP_CHAP)) { MAINDEBUG(("get_input: discarding proto 0x%x in phase %d", protocol, pppd_phase)); return; } /* * Upcall the proper protocol input routine. */ for (i = 0; (protp = protocols[i]) != NULL; ++i) { if (protp->protocol == protocol && protp->enabled_flag) { (*protp->input)(0, p, len); return; } if (protocol == (protp->protocol & ~0x8000) && protp->enabled_flag && protp->datainput != NULL) { (*protp->datainput)(0, p, len); return; } } if (debug) { const char *pname = protocol_name(protocol); if (pname != NULL) warn("Unsupported protocol '%s' (0x%x) received", pname, protocol); else warn("Unsupported protocol 0x%x received", protocol); } lcp_sprotrej(0, p - PPP_HDRLEN, len + PPP_HDRLEN); return; } /* * new_phase - signal the start of a new phase of pppd's operation. */ void new_phase(p) int p; { pppd_phase = p; if (new_phase_hook) (*new_phase_hook)(p); } /* * die - clean up state and exit with the specified status. */ void die(status) int status; { cleanup(); } /* * cleanup - restore anything which needs to be restored before we exit */ /* ARGSUSED */ static void cleanup() { sys_cleanup(); if (fd_ppp >= 0) disestablish_ppp(ttyfd); if (real_ttyfd >= 0) close_tty(); sys_close(); } /* * close_tty - restore the terminal device and close it. */ static void close_tty() { /* drop dtr to hang up */ if (!default_device && modem) { setdtr(real_ttyfd, 0); /* * This sleep is in case the serial port has CLOCAL set by default, * and consequently will reassert DTR when we close the device. */ sleep(1); } restore_tty(real_ttyfd); close(real_ttyfd); real_ttyfd = -1; } /* * update_link_stats - get stats at link termination. */ void update_link_stats(u) int u; { struct timeval now; char numbuf[32]; if (!get_ppp_stats(u, &link_stats) || gettimeofday(&now, NULL) < 0) return; link_connect_time = now.tv_sec - start_time.tv_sec; link_stats_valid = 1; slprintf(numbuf, sizeof(numbuf), "%d", link_connect_time); slprintf(numbuf, sizeof(numbuf), "%d", link_stats.bytes_out); slprintf(numbuf, sizeof(numbuf), "%d", link_stats.bytes_in); } struct callout { struct timeval c_time; /* time at which to call routine */ void *c_arg; /* argument to routine */ void (*c_func) __P((void *)); /* routine */ struct callout *c_next; }; static struct callout *callout = NULL; /* Callout list */ static struct timeval timenow; /* Current time */ /* * timeout - Schedule a timeout. * * Note that this timeout takes the number of seconds, NOT hz (as in * the kernel). */ void ppptimeout(func, arg, time) void (*func) __P((void *)); void *arg; int time; { struct callout *newp, *p, **pp; MAINDEBUG(("Timeout %p:%p in %d seconds.", func, arg, time)); /* * Allocate timeout. */ if ((newp = (struct callout *) malloc(sizeof(struct callout))) == NULL) fatal("Out of memory in timeout()!"); newp->c_arg = arg; newp->c_func = func; gettimeofday(&timenow, NULL); newp->c_time.tv_sec = timenow.tv_sec + time; newp->c_time.tv_usec = timenow.tv_usec; /* * Find correct place and link it in. */ for (pp = &callout; (p = *pp); pp = &p->c_next) if (newp->c_time.tv_sec < p->c_time.tv_sec || (newp->c_time.tv_sec == p->c_time.tv_sec && newp->c_time.tv_usec < p->c_time.tv_usec)) break; newp->c_next = p; *pp = newp; } /* * untimeout - Unschedule a timeout. */ void untimeout(func, arg) void (*func) __P((void *)); void *arg; { struct callout **copp, *freep; MAINDEBUG(("Untimeout %p:%p.", func, arg)); /* * Find first matching timeout and remove it from the list. */ for (copp = &callout; (freep = *copp); copp = &freep->c_next) if (freep->c_func == func && freep->c_arg == arg) { *copp = freep->c_next; free((char *) freep); break; } } /* * calltimeout - Call any timeout routines which are now due. */ static void calltimeout() { struct callout *p; while (callout != NULL) { p = callout; if (gettimeofday(&timenow, NULL) < 0) fatal("Failed to get time of day: %m"); if (!(p->c_time.tv_sec < timenow.tv_sec || (p->c_time.tv_sec == timenow.tv_sec && p->c_time.tv_usec <= timenow.tv_usec))) break; /* no, it's not time yet */ callout = p->c_next; (*p->c_func)(p->c_arg); free((char *) p); } } /* * timeleft - return the length of time until the next timeout is due. */ static struct timeval * timeleft(tvp) struct timeval *tvp; { if (callout == NULL) return NULL; gettimeofday(&timenow, NULL); tvp->tv_sec = callout->c_time.tv_sec - timenow.tv_sec; tvp->tv_usec = callout->c_time.tv_usec - timenow.tv_usec; if (tvp->tv_usec < 0) { tvp->tv_usec += 1000000; tvp->tv_sec -= 1; } if (tvp->tv_sec < 0) tvp->tv_sec = tvp->tv_usec = 0; return tvp; } /* * device_script - run a program to talk to the serial device * (e.g. to run the connector or disconnector script). */ static int device_script(int fd, int mode, char *program) { int iReturn = -1; char pScript[128]; /* copyt script into temporary location */ strcpy(pScript, program); /* check to see if dialer was initialized */ if ( !pppd_dialer ) { /* set default dialer to chatmain */ pppd_dialer = chatmain; } /* check to see if dialer is set */ if ( pppd_dialer ) { /* call the dialer */ iReturn = (*pppd_dialer)(fd, mode, program); } return ( -iReturn ); } /* * novm - log an error message saying we ran out of memory, and die. */ void novm(msg) char *msg; { fatal("Virtual memory exhausted allocating %s\n", msg); }