| /* |
| * tty.c - code for handling serial ports in pppd. |
| * |
| * Copyright (C) 2000-2004 Paul Mackerras. 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. The name(s) of the authors of this software must not be used to |
| * endorse or promote products derived from this software without |
| * prior written permission. |
| * |
| * 3. Redistributions of any form whatsoever must retain the following |
| * acknowledgment: |
| * "This product includes software developed by Paul Mackerras |
| * <paulus@samba.org>". |
| * |
| * THE AUTHORS OF THIS SOFTWARE DISCLAIM ALL WARRANTIES WITH REGARD TO |
| * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY |
| * AND FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY |
| * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN |
| * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING |
| * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| * |
| * Portions derived from main.c, which is: |
| * |
| * Copyright (c) 1984-2000 Carnegie Mellon University. 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. |
| * |
| * 3. The name "Carnegie Mellon University" must not be used to |
| * endorse or promote products derived from this software without |
| * prior written permission. For permission or any legal |
| * details, please contact |
| * Office of Technology Transfer |
| * Carnegie Mellon University |
| * 5000 Forbes Avenue |
| * Pittsburgh, PA 15213-3890 |
| * (412) 268-4387, fax: (412) 268-7395 |
| * tech-transfer@andrew.cmu.edu |
| * |
| * 4. Redistributions of any form whatsoever must retain the following |
| * acknowledgment: |
| * "This product includes software developed by Computing Services |
| * at Carnegie Mellon University (http://www.cmu.edu/computing/)." |
| * |
| * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO |
| * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY |
| * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE |
| * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN |
| * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING |
| * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| */ |
| |
| #define RCSID "$Id: tty.c,v 1.22 2004/11/13 12:07:29 paulus Exp $" |
| |
| #include <stdio.h> |
| #include <ctype.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <signal.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <syslog.h> |
| #include <netdb.h> |
| #include <utmp.h> |
| #include <pwd.h> |
| #include <setjmp.h> |
| #include <sys/param.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <sys/time.h> |
| #include <sys/resource.h> |
| #include <sys/stat.h> |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <arpa/inet.h> |
| |
| #include "pppd.h" |
| #include "fsm.h" |
| #include "lcp.h" |
| |
| void tty_process_extra_options __P((void)); |
| void tty_check_options __P((void)); |
| int connect_tty __P((void)); |
| void disconnect_tty __P((void)); |
| void tty_close_fds __P((void)); |
| void cleanup_tty __P((void)); |
| void tty_do_send_config __P((int, u_int32_t, int, int)); |
| |
| static int setdevname __P((char *, char **, int)); |
| static int setspeed __P((char *, char **, int)); |
| static int setxonxoff __P((char **)); |
| static int setescape __P((char **)); |
| static void printescape __P((option_t *, void (*)(void *, char *,...),void *)); |
| static void finish_tty __P((void)); |
| static int start_charshunt __P((int, int)); |
| static void stop_charshunt __P((void *, int)); |
| static void charshunt_done __P((void *)); |
| static void charshunt __P((int, int, char *)); |
| static int record_write __P((FILE *, int code, u_char *buf, int nb, |
| struct timeval *)); |
| static int open_socket __P((char *)); |
| static void maybe_relock __P((void *, int)); |
| |
| 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) */ |
| static int ttyfd; /* Serial port file descriptor */ |
| static char speed_str[16]; /* Serial port speed as string */ |
| |
| mode_t tty_mode = (mode_t)-1; /* Original access permissions to tty */ |
| int baud_rate; /* Actual bits/second for serial device */ |
| char *callback_script; /* script for doing callback */ |
| int charshunt_pid; /* Process ID for charshunt */ |
| int locked; /* lock() has succeeded */ |
| struct stat devstat; /* result of stat() on devnam */ |
| |
| /* option variables */ |
| int crtscts = 0; /* Use hardware flow control */ |
| bool modem = 1; /* Use modem control lines */ |
| int inspeed = 0; /* Input/Output speed requested */ |
| bool lockflag = 0; /* Create lock file to lock the serial dev */ |
| char *initializer = NULL; /* Script to initialize physical link */ |
| char *connect_script = NULL; /* Script to establish physical link */ |
| char *disconnect_script = NULL; /* Script to disestablish physical link */ |
| char *welcomer = NULL; /* Script to run after phys link estab. */ |
| char *ptycommand = NULL; /* Command to run on other side of pty */ |
| bool notty = 0; /* Stdin/out is not a tty */ |
| char *record_file = NULL; /* File to record chars sent/received */ |
| int max_data_rate; /* max bytes/sec through charshunt */ |
| bool sync_serial = 0; /* Device is synchronous serial device */ |
| char *pty_socket = NULL; /* Socket to connect to pty */ |
| int using_pty = 0; /* we're allocating a pty as the device */ |
| |
| extern uid_t uid; |
| extern int kill_link; |
| extern int asked_to_quit; |
| extern int got_sigterm; |
| |
| /* XXX */ |
| extern int privopen; /* don't lock, open device as root */ |
| |
| u_int32_t xmit_accm[8]; /* extended transmit ACCM */ |
| |
| /* option descriptors */ |
| option_t tty_options[] = { |
| /* device name must be first, or change connect_tty() below! */ |
| { "device name", o_wild, (void *) &setdevname, |
| "Serial port device name", |
| OPT_DEVNAM | OPT_PRIVFIX | OPT_NOARG | OPT_A2STRVAL | OPT_STATIC, |
| devnam}, |
| |
| { "tty speed", o_wild, (void *) &setspeed, |
| "Baud rate for serial port", |
| OPT_PRIO | OPT_NOARG | OPT_A2STRVAL | OPT_STATIC, speed_str }, |
| |
| { "lock", o_bool, &lockflag, |
| "Lock serial device with UUCP-style lock file", OPT_PRIO | 1 }, |
| { "nolock", o_bool, &lockflag, |
| "Don't lock serial device", OPT_PRIOSUB | OPT_PRIV }, |
| |
| { "init", o_string, &initializer, |
| "A program to initialize the device", OPT_PRIO | OPT_PRIVFIX }, |
| |
| { "connect", o_string, &connect_script, |
| "A program to set up a connection", OPT_PRIO | OPT_PRIVFIX }, |
| |
| { "disconnect", o_string, &disconnect_script, |
| "Program to disconnect serial device", OPT_PRIO | OPT_PRIVFIX }, |
| |
| { "welcome", o_string, &welcomer, |
| "Script to welcome client", OPT_PRIO | OPT_PRIVFIX }, |
| |
| { "pty", o_string, &ptycommand, |
| "Script to run on pseudo-tty master side", |
| OPT_PRIO | OPT_PRIVFIX | OPT_DEVNAM }, |
| |
| { "notty", o_bool, ¬ty, |
| "Input/output is not a tty", OPT_DEVNAM | 1 }, |
| |
| { "socket", o_string, &pty_socket, |
| "Send and receive over socket, arg is host:port", |
| OPT_PRIO | OPT_DEVNAM }, |
| |
| { "record", o_string, &record_file, |
| "Record characters sent/received to file", OPT_PRIO }, |
| |
| { "crtscts", o_int, &crtscts, |
| "Set hardware (RTS/CTS) flow control", |
| OPT_PRIO | OPT_NOARG | OPT_VAL(1) }, |
| { "cdtrcts", o_int, &crtscts, |
| "Set alternate hardware (DTR/CTS) flow control", |
| OPT_PRIOSUB | OPT_NOARG | OPT_VAL(2) }, |
| { "nocrtscts", o_int, &crtscts, |
| "Disable hardware flow control", |
| OPT_PRIOSUB | OPT_NOARG | OPT_VAL(-1) }, |
| { "-crtscts", o_int, &crtscts, |
| "Disable hardware flow control", |
| OPT_PRIOSUB | OPT_ALIAS | OPT_NOARG | OPT_VAL(-1) }, |
| { "nocdtrcts", o_int, &crtscts, |
| "Disable hardware flow control", |
| OPT_PRIOSUB | OPT_ALIAS | OPT_NOARG | OPT_VAL(-1) }, |
| { "xonxoff", o_special_noarg, (void *)setxonxoff, |
| "Set software (XON/XOFF) flow control", OPT_PRIOSUB }, |
| |
| { "modem", o_bool, &modem, |
| "Use modem control lines", OPT_PRIO | 1 }, |
| { "local", o_bool, &modem, |
| "Don't use modem control lines", OPT_PRIOSUB | 0 }, |
| |
| { "sync", o_bool, &sync_serial, |
| "Use synchronous HDLC serial encoding", 1 }, |
| |
| { "datarate", o_int, &max_data_rate, |
| "Maximum data rate in bytes/sec (with pty, notty or record option)", |
| OPT_PRIO }, |
| |
| { "escape", o_special, (void *)setescape, |
| "List of character codes to escape on transmission", |
| OPT_A2PRINTER, (void *)printescape }, |
| |
| { NULL } |
| }; |
| |
| |
| struct channel tty_channel = { |
| tty_options, |
| &tty_process_extra_options, |
| &tty_check_options, |
| &connect_tty, |
| &disconnect_tty, |
| &tty_establish_ppp, |
| &tty_disestablish_ppp, |
| &tty_do_send_config, |
| &tty_recv_config, |
| &cleanup_tty, |
| &tty_close_fds |
| }; |
| |
| /* |
| * setspeed - Set the serial port baud rate. |
| * If doit is 0, the call is to check whether this option is |
| * potentially a speed value. |
| */ |
| static int |
| setspeed(arg, argv, doit) |
| char *arg; |
| char **argv; |
| int doit; |
| { |
| char *ptr; |
| int spd; |
| |
| spd = strtol(arg, &ptr, 0); |
| if (ptr == arg || *ptr != 0 || spd == 0) |
| return 0; |
| if (doit) { |
| inspeed = spd; |
| slprintf(speed_str, sizeof(speed_str), "%d", spd); |
| } |
| return 1; |
| } |
| |
| |
| /* |
| * setdevname - Set the device name. |
| * If doit is 0, the call is to check whether this option is |
| * potentially a device name. |
| */ |
| static int |
| setdevname(cp, argv, doit) |
| char *cp; |
| char **argv; |
| int doit; |
| { |
| struct stat statbuf; |
| char dev[MAXPATHLEN]; |
| |
| if (*cp == 0) |
| return 0; |
| |
| if (*cp != '/') { |
| strlcpy(dev, "/dev/", sizeof(dev)); |
| strlcat(dev, cp, sizeof(dev)); |
| cp = dev; |
| } |
| |
| /* |
| * Check if there is a character device by this name. |
| */ |
| if (stat(cp, &statbuf) < 0) { |
| if (!doit) |
| return errno != ENOENT; |
| option_error("Couldn't stat %s: %m", cp); |
| return 0; |
| } |
| if (!S_ISCHR(statbuf.st_mode)) { |
| if (doit) |
| option_error("%s is not a character device", cp); |
| return 0; |
| } |
| |
| if (doit) { |
| strlcpy(devnam, cp, sizeof(devnam)); |
| devstat = statbuf; |
| default_device = 0; |
| } |
| |
| return 1; |
| } |
| |
| static int |
| setxonxoff(argv) |
| char **argv; |
| { |
| lcp_wantoptions[0].asyncmap |= 0x000A0000; /* escape ^S and ^Q */ |
| lcp_wantoptions[0].neg_asyncmap = 1; |
| |
| crtscts = -2; |
| return 1; |
| } |
| |
| /* |
| * setescape - add chars to the set we escape on transmission. |
| */ |
| static int |
| setescape(argv) |
| char **argv; |
| { |
| int n, ret; |
| char *p, *endp; |
| |
| p = *argv; |
| ret = 1; |
| while (*p) { |
| n = strtol(p, &endp, 16); |
| if (p == endp) { |
| option_error("escape parameter contains invalid hex number '%s'", |
| p); |
| return 0; |
| } |
| p = endp; |
| if (n < 0 || n == 0x5E || n > 0xFF) { |
| option_error("can't escape character 0x%x", n); |
| ret = 0; |
| } else |
| xmit_accm[n >> 5] |= 1 << (n & 0x1F); |
| while (*p == ',' || *p == ' ') |
| ++p; |
| } |
| lcp_allowoptions[0].asyncmap = xmit_accm[0]; |
| return ret; |
| } |
| |
| static void |
| printescape(opt, printer, arg) |
| option_t *opt; |
| void (*printer) __P((void *, char *, ...)); |
| void *arg; |
| { |
| int n; |
| int first = 1; |
| |
| for (n = 0; n < 256; ++n) { |
| if (n == 0x7d) |
| n += 2; /* skip 7d, 7e */ |
| if (xmit_accm[n >> 5] & (1 << (n & 0x1f))) { |
| if (!first) |
| printer(arg, ","); |
| else |
| first = 0; |
| printer(arg, "%x", n); |
| } |
| } |
| if (first) |
| printer(arg, "oops # nothing escaped"); |
| } |
| |
| /* |
| * tty_init - do various tty-related initializations. |
| */ |
| void tty_init() |
| { |
| add_notifier(&pidchange, maybe_relock, 0); |
| the_channel = &tty_channel; |
| xmit_accm[3] = 0x60000000; |
| } |
| |
| /* |
| * tty_process_extra_options - work out which tty device we are using |
| * and read its options file. |
| */ |
| void tty_process_extra_options() |
| { |
| using_pty = notty || ptycommand != NULL || pty_socket != NULL; |
| if (using_pty) |
| return; |
| if (default_device) { |
| char *p; |
| if (!isatty(0) || (p = ttyname(0)) == NULL) { |
| option_error("no device specified and stdin is not a tty"); |
| exit(EXIT_OPTION_ERROR); |
| } |
| strlcpy(devnam, p, sizeof(devnam)); |
| if (stat(devnam, &devstat) < 0) |
| fatal("Couldn't stat default device %s: %m", devnam); |
| } |
| |
| |
| /* |
| * Parse the tty options file. |
| * The per-tty options file should not change |
| * ptycommand, pty_socket, notty or devnam. |
| * options_for_tty doesn't override options set on the command line, |
| * except for some privileged options. |
| */ |
| if (!options_for_tty()) |
| exit(EXIT_OPTION_ERROR); |
| } |
| |
| /* |
| * tty_check_options - do consistency checks on the options we were given. |
| */ |
| void |
| tty_check_options() |
| { |
| struct stat statbuf; |
| int fdflags; |
| |
| if (demand && notty) { |
| option_error("demand-dialling is incompatible with notty"); |
| exit(EXIT_OPTION_ERROR); |
| } |
| if (demand && connect_script == 0 && ptycommand == NULL |
| && pty_socket == NULL) { |
| option_error("connect script is required for demand-dialling\n"); |
| exit(EXIT_OPTION_ERROR); |
| } |
| /* default holdoff to 0 if no connect script has been given */ |
| if (connect_script == 0 && !holdoff_specified) |
| holdoff = 0; |
| |
| if (using_pty) { |
| if (!default_device) { |
| option_error("%s option precludes specifying device name", |
| pty_socket? "socket": notty? "notty": "pty"); |
| exit(EXIT_OPTION_ERROR); |
| } |
| if (ptycommand != NULL && notty) { |
| option_error("pty option is incompatible with notty option"); |
| exit(EXIT_OPTION_ERROR); |
| } |
| if (pty_socket != NULL && (ptycommand != NULL || notty)) { |
| option_error("socket option is incompatible with pty and notty"); |
| exit(EXIT_OPTION_ERROR); |
| } |
| default_device = notty; |
| lockflag = 0; |
| modem = 0; |
| if (notty && log_to_fd <= 1) |
| log_to_fd = -1; |
| } else { |
| /* |
| * If the user has specified a device which is the same as |
| * the one on stdin, pretend they didn't specify any. |
| * If the device is already open read/write on stdin, |
| * we assume we don't need to lock it, and we can open it |
| * as root. |
| */ |
| if (fstat(0, &statbuf) >= 0 && S_ISCHR(statbuf.st_mode) |
| && statbuf.st_rdev == devstat.st_rdev) { |
| default_device = 1; |
| fdflags = fcntl(0, F_GETFL); |
| if (fdflags != -1 && (fdflags & O_ACCMODE) == O_RDWR) |
| privopen = 1; |
| } |
| } |
| if (default_device) |
| nodetach = 1; |
| |
| /* |
| * Don't send log messages to the serial port, it tends to |
| * confuse the peer. :-) |
| */ |
| if (log_to_fd >= 0 && fstat(log_to_fd, &statbuf) >= 0 |
| && S_ISCHR(statbuf.st_mode) && statbuf.st_rdev == devstat.st_rdev) |
| log_to_fd = -1; |
| } |
| |
| /* |
| * connect_tty - get the serial port ready to start doing PPP. |
| * That is, open the serial port, set its speed and mode, and run |
| * the connector and/or welcomer. |
| */ |
| int connect_tty() |
| { |
| char *connector; |
| int fdflags; |
| #ifndef __linux__ |
| struct stat statbuf; |
| #endif |
| char numbuf[16]; |
| |
| /* |
| * Get a pty master/slave pair if the pty, notty, socket, |
| * or record options were specified. |
| */ |
| strlcpy(ppp_devnam, devnam, sizeof(ppp_devnam)); |
| pty_master = -1; |
| pty_slave = -1; |
| real_ttyfd = -1; |
| if (using_pty || record_file != NULL) { |
| if (!get_pty(&pty_master, &pty_slave, ppp_devnam, uid)) { |
| error("Couldn't allocate pseudo-tty"); |
| status = EXIT_FATAL_ERROR; |
| return -1; |
| } |
| set_up_tty(pty_slave, 1); |
| } |
| |
| /* |
| * Lock the device if we've been asked to. |
| */ |
| status = EXIT_LOCK_FAILED; |
| if (lockflag && !privopen) { |
| if (lock(devnam) < 0) |
| goto errret; |
| locked = 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; |
| got_sigterm = 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, prio; |
| |
| prio = privopen? OPRIO_ROOT: tty_options[0].priority; |
| if (prio < OPRIO_ROOT) |
| seteuid(uid); |
| real_ttyfd = open(devnam, O_NONBLOCK | O_RDWR, 0); |
| err = errno; |
| if (prio < OPRIO_ROOT) |
| seteuid(0); |
| if (real_ttyfd >= 0) |
| break; |
| errno = err; |
| if (err != EINTR) { |
| error("Failed to open %s: %m", devnam); |
| status = EXIT_OPEN_FAILED; |
| } |
| if (!persist || err != EINTR) |
| goto errret; |
| } |
| ttyfd = real_ttyfd; |
| 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"); |
| |
| #ifndef __linux__ |
| /* |
| * Linux 2.4 and above blocks normal writes to the tty |
| * when it is in PPP line discipline, so this isn't needed. |
| */ |
| /* |
| * Do the equivalent of `mesg n' to stop broadcast messages. |
| */ |
| if (fstat(ttyfd, &statbuf) < 0 |
| || fchmod(ttyfd, statbuf.st_mode & ~(S_IWGRP | S_IWOTH)) < 0) { |
| warn("Couldn't restrict write permissions to %s: %m", devnam); |
| } else |
| tty_mode = statbuf.st_mode; |
| #endif /* __linux__ */ |
| |
| /* |
| * 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)); |
| } |
| |
| /* |
| * If the pty, socket, notty and/or record option was specified, |
| * start up the character shunt now. |
| */ |
| status = EXIT_PTYCMD_FAILED; |
| if (ptycommand != NULL) { |
| if (record_file != NULL) { |
| int ipipe[2], opipe[2], ok; |
| |
| if (pipe(ipipe) < 0 || pipe(opipe) < 0) |
| fatal("Couldn't create pipes for record option: %m"); |
| |
| /* don't leak these to the ptycommand */ |
| (void) fcntl(ipipe[0], F_SETFD, FD_CLOEXEC); |
| (void) fcntl(opipe[1], F_SETFD, FD_CLOEXEC); |
| |
| ok = device_script(ptycommand, opipe[0], ipipe[1], 1) == 0 |
| && start_charshunt(ipipe[0], opipe[1]); |
| close(ipipe[0]); |
| close(ipipe[1]); |
| close(opipe[0]); |
| close(opipe[1]); |
| if (!ok) |
| goto errret; |
| } else { |
| if (device_script(ptycommand, pty_master, pty_master, 1) < 0) |
| goto errret; |
| } |
| } else if (pty_socket != NULL) { |
| int fd = open_socket(pty_socket); |
| if (fd < 0) |
| goto errret; |
| if (!start_charshunt(fd, fd)) |
| goto errret; |
| close(fd); |
| } else if (notty) { |
| if (!start_charshunt(0, 1)) |
| goto errret; |
| dup2(fd_devnull, 0); |
| dup2(fd_devnull, 1); |
| if (log_to_fd == 1) |
| log_to_fd = -1; |
| if (log_to_fd != 2) |
| dup2(fd_devnull, 2); |
| } else if (record_file != NULL) { |
| int fd = dup(ttyfd); |
| if (!start_charshunt(fd, fd)) |
| goto errret; |
| } |
| |
| if (using_pty || record_file != NULL) { |
| ttyfd = pty_slave; |
| close(pty_master); |
| pty_master = -1; |
| } |
| |
| /* 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(initializer, ttyfd, ttyfd, 0) < 0) { |
| error("Initializer script failed"); |
| status = EXIT_INIT_FAILED; |
| goto errret; |
| } |
| if (got_sigterm) { |
| disconnect_tty(); |
| goto errret; |
| } |
| info("Serial port initialized."); |
| } |
| |
| if (connector && connector[0]) { |
| if (device_script(connector, ttyfd, ttyfd, 0) < 0) { |
| error("Connect script failed"); |
| status = EXIT_CONNECT_FAILED; |
| goto errret; |
| } |
| if (got_sigterm) { |
| disconnect_tty(); |
| goto errret; |
| } |
| 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) { |
| int i; |
| for (;;) { |
| if ((i = open(devnam, O_RDWR)) >= 0) |
| break; |
| if (errno != EINTR) { |
| error("Failed to reopen %s: %m", devnam); |
| status = EXIT_OPEN_FAILED; |
| } |
| if (!persist || errno != EINTR || hungup || got_sigterm) |
| goto errret; |
| } |
| close(i); |
| } |
| |
| slprintf(numbuf, sizeof(numbuf), "%d", baud_rate); |
| script_setenv("SPEED", numbuf, 0); |
| |
| /* run welcome script, if any */ |
| if (welcomer && welcomer[0]) { |
| if (device_script(welcomer, ttyfd, ttyfd, 0) < 0) |
| warn("Welcome script failed"); |
| } |
| |
| /* |
| * If we are initiating this connection, wait for a short |
| * time for something from the peer. This can avoid bouncing |
| * our packets off his tty before he has it set up. |
| */ |
| if (connector != NULL || ptycommand != NULL || pty_socket != NULL) |
| listen_time = connect_delay; |
| |
| return ttyfd; |
| |
| errret: |
| if (pty_master >= 0) { |
| close(pty_master); |
| pty_master = -1; |
| } |
| if (pty_slave >= 0) { |
| close(pty_slave); |
| pty_slave = -1; |
| } |
| if (real_ttyfd >= 0) { |
| close(real_ttyfd); |
| real_ttyfd = -1; |
| } |
| ttyfd = -1; |
| if (got_sigterm) |
| asked_to_quit = 1; |
| return -1; |
| } |
| |
| |
| void disconnect_tty() |
| { |
| if (disconnect_script == NULL || hungup) |
| return; |
| if (real_ttyfd >= 0) |
| set_up_tty(real_ttyfd, 1); |
| if (device_script(disconnect_script, ttyfd, ttyfd, 0) < 0) { |
| warn("disconnect script failed"); |
| } else { |
| info("Serial link disconnected."); |
| } |
| } |
| |
| void tty_close_fds() |
| { |
| if (pty_slave >= 0) |
| close(pty_slave); |
| if (real_ttyfd >= 0) { |
| close(real_ttyfd); |
| real_ttyfd = -1; |
| } |
| /* N.B. ttyfd will == either pty_slave or real_ttyfd */ |
| } |
| |
| void cleanup_tty() |
| { |
| if (real_ttyfd >= 0) |
| finish_tty(); |
| tty_close_fds(); |
| if (locked) { |
| unlock(); |
| locked = 0; |
| } |
| } |
| |
| /* |
| * tty_do_send_config - set transmit-side PPP configuration. |
| * We set the extended transmit ACCM here as well. |
| */ |
| void |
| tty_do_send_config(mtu, accm, pcomp, accomp) |
| int mtu; |
| u_int32_t accm; |
| int pcomp, accomp; |
| { |
| tty_set_xaccm(xmit_accm); |
| tty_send_config(mtu, accm, pcomp, accomp); |
| } |
| |
| /* |
| * finish_tty - restore the terminal device to its original settings |
| */ |
| static void |
| finish_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); |
| |
| #ifndef __linux__ |
| if (tty_mode != (mode_t) -1) { |
| if (fchmod(real_ttyfd, tty_mode) != 0) |
| error("Couldn't restore tty permissions"); |
| } |
| #endif /* __linux__ */ |
| |
| close(real_ttyfd); |
| real_ttyfd = -1; |
| } |
| |
| /* |
| * maybe_relock - our PID has changed, maybe update the lock file. |
| */ |
| static void |
| maybe_relock(arg, pid) |
| void *arg; |
| int pid; |
| { |
| if (locked) |
| relock(pid); |
| } |
| |
| /* |
| * open_socket - establish a stream socket connection to the nominated |
| * host and port. |
| */ |
| static int |
| open_socket(dest) |
| char *dest; |
| { |
| char *sep, *endp = NULL; |
| int sock, port = -1; |
| u_int32_t host; |
| struct hostent *hent; |
| struct sockaddr_in sad; |
| |
| /* parse host:port and resolve host to an IP address */ |
| sep = strchr(dest, ':'); |
| if (sep != NULL) |
| port = strtol(sep+1, &endp, 10); |
| if (port < 0 || endp == sep+1 || sep == dest) { |
| error("Can't parse host:port for socket destination"); |
| return -1; |
| } |
| *sep = 0; |
| host = inet_addr(dest); |
| if (host == (u_int32_t) -1) { |
| hent = gethostbyname(dest); |
| if (hent == NULL) { |
| error("%s: unknown host in socket option", dest); |
| *sep = ':'; |
| return -1; |
| } |
| host = *(u_int32_t *)(hent->h_addr_list[0]); |
| } |
| *sep = ':'; |
| |
| /* get a socket and connect it to the other end */ |
| sock = socket(PF_INET, SOCK_STREAM, 0); |
| if (sock < 0) { |
| error("Can't create socket: %m"); |
| return -1; |
| } |
| memset(&sad, 0, sizeof(sad)); |
| sad.sin_family = AF_INET; |
| sad.sin_port = htons(port); |
| sad.sin_addr.s_addr = host; |
| if (connect(sock, (struct sockaddr *)&sad, sizeof(sad)) < 0) { |
| error("Can't connect to %s: %m", dest); |
| close(sock); |
| return -1; |
| } |
| |
| return sock; |
| } |
| |
| |
| /* |
| * start_charshunt - create a child process to run the character shunt. |
| */ |
| static int |
| start_charshunt(ifd, ofd) |
| int ifd, ofd; |
| { |
| int cpid; |
| |
| cpid = safe_fork(ifd, ofd, (log_to_fd >= 0? log_to_fd: 2)); |
| if (cpid == -1) { |
| error("Can't fork process for character shunt: %m"); |
| return 0; |
| } |
| if (cpid == 0) { |
| /* child */ |
| reopen_log(); |
| if (!nodetach) |
| log_to_fd = -1; |
| else if (log_to_fd >= 0) |
| log_to_fd = 2; |
| setgid(getgid()); |
| setuid(uid); |
| if (getuid() != uid) |
| fatal("setuid failed"); |
| charshunt(0, 1, record_file); |
| exit(0); |
| } |
| charshunt_pid = cpid; |
| add_notifier(&sigreceived, stop_charshunt, 0); |
| record_child(cpid, "pppd (charshunt)", charshunt_done, NULL); |
| return 1; |
| } |
| |
| static void |
| charshunt_done(arg) |
| void *arg; |
| { |
| charshunt_pid = 0; |
| } |
| |
| static void |
| stop_charshunt(arg, sig) |
| void *arg; |
| int sig; |
| { |
| if (charshunt_pid) |
| kill(charshunt_pid, (sig == SIGINT? sig: SIGTERM)); |
| } |
| |
| /* |
| * charshunt - the character shunt, which passes characters between |
| * the pty master side and the serial port (or stdin/stdout). |
| * This runs as the user (not as root). |
| * (We assume ofd >= ifd which is true the way this gets called. :-). |
| */ |
| static void |
| charshunt(ifd, ofd, record_file) |
| int ifd, ofd; |
| char *record_file; |
| { |
| int n, nfds; |
| fd_set ready, writey; |
| u_char *ibufp, *obufp; |
| int nibuf, nobuf; |
| int flags; |
| int pty_readable, stdin_readable; |
| struct timeval lasttime; |
| FILE *recordf = NULL; |
| int ilevel, olevel, max_level; |
| struct timeval levelt, tout, *top; |
| extern u_char inpacket_buf[]; |
| |
| /* |
| * Reset signal handlers. |
| */ |
| signal(SIGHUP, SIG_IGN); /* Hangup */ |
| signal(SIGINT, SIG_DFL); /* Interrupt */ |
| signal(SIGTERM, SIG_DFL); /* Terminate */ |
| signal(SIGCHLD, SIG_DFL); |
| signal(SIGUSR1, SIG_DFL); |
| signal(SIGUSR2, SIG_DFL); |
| signal(SIGABRT, SIG_DFL); |
| signal(SIGALRM, SIG_DFL); |
| signal(SIGFPE, SIG_DFL); |
| signal(SIGILL, SIG_DFL); |
| signal(SIGPIPE, SIG_DFL); |
| signal(SIGQUIT, SIG_DFL); |
| signal(SIGSEGV, SIG_DFL); |
| #ifdef SIGBUS |
| signal(SIGBUS, SIG_DFL); |
| #endif |
| #ifdef SIGEMT |
| signal(SIGEMT, SIG_DFL); |
| #endif |
| #ifdef SIGPOLL |
| signal(SIGPOLL, SIG_DFL); |
| #endif |
| #ifdef SIGPROF |
| signal(SIGPROF, SIG_DFL); |
| #endif |
| #ifdef SIGSYS |
| signal(SIGSYS, SIG_DFL); |
| #endif |
| #ifdef SIGTRAP |
| signal(SIGTRAP, SIG_DFL); |
| #endif |
| #ifdef SIGVTALRM |
| signal(SIGVTALRM, SIG_DFL); |
| #endif |
| #ifdef SIGXCPU |
| signal(SIGXCPU, SIG_DFL); |
| #endif |
| #ifdef SIGXFSZ |
| signal(SIGXFSZ, SIG_DFL); |
| #endif |
| |
| /* |
| * Check that the fds won't overrun the fd_sets |
| */ |
| if (ifd >= FD_SETSIZE || ofd >= FD_SETSIZE || pty_master >= FD_SETSIZE) |
| fatal("internal error: file descriptor too large (%d, %d, %d)", |
| ifd, ofd, pty_master); |
| |
| /* |
| * Open the record file if required. |
| */ |
| if (record_file != NULL) { |
| recordf = fopen(record_file, "a"); |
| if (recordf == NULL) |
| error("Couldn't create record file %s: %m", record_file); |
| } |
| |
| /* set all the fds to non-blocking mode */ |
| flags = fcntl(pty_master, F_GETFL); |
| if (flags == -1 |
| || fcntl(pty_master, F_SETFL, flags | O_NONBLOCK) == -1) |
| warn("couldn't set pty master to nonblock: %m"); |
| flags = fcntl(ifd, F_GETFL); |
| if (flags == -1 |
| || fcntl(ifd, F_SETFL, flags | O_NONBLOCK) == -1) |
| warn("couldn't set %s to nonblock: %m", (ifd==0? "stdin": "tty")); |
| if (ofd != ifd) { |
| flags = fcntl(ofd, F_GETFL); |
| if (flags == -1 |
| || fcntl(ofd, F_SETFL, flags | O_NONBLOCK) == -1) |
| warn("couldn't set stdout to nonblock: %m"); |
| } |
| |
| nibuf = nobuf = 0; |
| ibufp = obufp = NULL; |
| pty_readable = stdin_readable = 1; |
| |
| ilevel = olevel = 0; |
| gettimeofday(&levelt, NULL); |
| if (max_data_rate) { |
| max_level = max_data_rate / 10; |
| if (max_level < 100) |
| max_level = 100; |
| } else |
| max_level = PPP_MRU + PPP_HDRLEN + 1; |
| |
| nfds = (ofd > pty_master? ofd: pty_master) + 1; |
| if (recordf != NULL) { |
| gettimeofday(&lasttime, NULL); |
| putc(7, recordf); /* put start marker */ |
| putc(lasttime.tv_sec >> 24, recordf); |
| putc(lasttime.tv_sec >> 16, recordf); |
| putc(lasttime.tv_sec >> 8, recordf); |
| putc(lasttime.tv_sec, recordf); |
| lasttime.tv_usec = 0; |
| } |
| |
| while (nibuf != 0 || nobuf != 0 || pty_readable || stdin_readable) { |
| top = 0; |
| tout.tv_sec = 0; |
| tout.tv_usec = 10000; |
| FD_ZERO(&ready); |
| FD_ZERO(&writey); |
| if (nibuf != 0) { |
| if (ilevel >= max_level) |
| top = &tout; |
| else |
| FD_SET(pty_master, &writey); |
| } else if (stdin_readable) |
| FD_SET(ifd, &ready); |
| if (nobuf != 0) { |
| if (olevel >= max_level) |
| top = &tout; |
| else |
| FD_SET(ofd, &writey); |
| } else if (pty_readable) |
| FD_SET(pty_master, &ready); |
| if (select(nfds, &ready, &writey, NULL, top) < 0) { |
| if (errno != EINTR) |
| fatal("select"); |
| continue; |
| } |
| if (max_data_rate) { |
| double dt; |
| int nbt; |
| struct timeval now; |
| |
| gettimeofday(&now, NULL); |
| dt = (now.tv_sec - levelt.tv_sec |
| + (now.tv_usec - levelt.tv_usec) / 1e6); |
| nbt = (int)(dt * max_data_rate); |
| ilevel = (nbt < 0 || nbt > ilevel)? 0: ilevel - nbt; |
| olevel = (nbt < 0 || nbt > olevel)? 0: olevel - nbt; |
| levelt = now; |
| } else |
| ilevel = olevel = 0; |
| if (FD_ISSET(ifd, &ready)) { |
| ibufp = inpacket_buf; |
| nibuf = read(ifd, ibufp, PPP_MRU + PPP_HDRLEN); |
| if (nibuf < 0 && errno == EIO) |
| nibuf = 0; |
| if (nibuf < 0) { |
| if (!(errno == EINTR || errno == EAGAIN)) { |
| error("Error reading standard input: %m"); |
| break; |
| } |
| nibuf = 0; |
| } else if (nibuf == 0) { |
| /* end of file from stdin */ |
| stdin_readable = 0; |
| if (recordf) |
| if (!record_write(recordf, 4, NULL, 0, &lasttime)) |
| recordf = NULL; |
| } else { |
| FD_SET(pty_master, &writey); |
| if (recordf) |
| if (!record_write(recordf, 2, ibufp, nibuf, &lasttime)) |
| recordf = NULL; |
| } |
| } |
| if (FD_ISSET(pty_master, &ready)) { |
| obufp = outpacket_buf; |
| nobuf = read(pty_master, obufp, PPP_MRU + PPP_HDRLEN); |
| if (nobuf < 0 && errno == EIO) |
| nobuf = 0; |
| if (nobuf < 0) { |
| if (!(errno == EINTR || errno == EAGAIN)) { |
| error("Error reading pseudo-tty master: %m"); |
| break; |
| } |
| nobuf = 0; |
| } else if (nobuf == 0) { |
| /* end of file from the pty - slave side has closed */ |
| pty_readable = 0; |
| stdin_readable = 0; /* pty is not writable now */ |
| nibuf = 0; |
| close(ofd); |
| if (recordf) |
| if (!record_write(recordf, 3, NULL, 0, &lasttime)) |
| recordf = NULL; |
| } else { |
| FD_SET(ofd, &writey); |
| if (recordf) |
| if (!record_write(recordf, 1, obufp, nobuf, &lasttime)) |
| recordf = NULL; |
| } |
| } else if (!stdin_readable) |
| pty_readable = 0; |
| if (FD_ISSET(ofd, &writey)) { |
| n = nobuf; |
| if (olevel + n > max_level) |
| n = max_level - olevel; |
| n = write(ofd, obufp, n); |
| if (n < 0) { |
| if (errno == EIO) { |
| pty_readable = 0; |
| nobuf = 0; |
| } else if (errno != EAGAIN && errno != EINTR) { |
| error("Error writing standard output: %m"); |
| break; |
| } |
| } else { |
| obufp += n; |
| nobuf -= n; |
| olevel += n; |
| } |
| } |
| if (FD_ISSET(pty_master, &writey)) { |
| n = nibuf; |
| if (ilevel + n > max_level) |
| n = max_level - ilevel; |
| n = write(pty_master, ibufp, n); |
| if (n < 0) { |
| if (errno == EIO) { |
| stdin_readable = 0; |
| nibuf = 0; |
| } else if (errno != EAGAIN && errno != EINTR) { |
| error("Error writing pseudo-tty master: %m"); |
| break; |
| } |
| } else { |
| ibufp += n; |
| nibuf -= n; |
| ilevel += n; |
| } |
| } |
| } |
| exit(0); |
| } |
| |
| static int |
| record_write(f, code, buf, nb, tp) |
| FILE *f; |
| int code; |
| u_char *buf; |
| int nb; |
| struct timeval *tp; |
| { |
| struct timeval now; |
| int diff; |
| |
| gettimeofday(&now, NULL); |
| now.tv_usec /= 100000; /* actually 1/10 s, not usec now */ |
| diff = (now.tv_sec - tp->tv_sec) * 10 + (now.tv_usec - tp->tv_usec); |
| if (diff > 0) { |
| if (diff > 255) { |
| putc(5, f); |
| putc(diff >> 24, f); |
| putc(diff >> 16, f); |
| putc(diff >> 8, f); |
| putc(diff, f); |
| } else { |
| putc(6, f); |
| putc(diff, f); |
| } |
| *tp = now; |
| } |
| putc(code, f); |
| if (buf != NULL) { |
| putc(nb >> 8, f); |
| putc(nb, f); |
| fwrite(buf, nb, 1, f); |
| } |
| fflush(f); |
| if (ferror(f)) { |
| error("Error writing record file: %m"); |
| return 0; |
| } |
| return 1; |
| } |