📄 tty.c
字号:
/* * tty.c - code for handling serial ports in pppd. * * Copyright (C) 2000 Paul Mackerras. * All rights reserved. * * Portions 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: tty.c,v 1.6 2001/03/12 22:59:01 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;/* 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 intsetspeed(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 intsetdevname(cp, argv, doit) char *cp; char **argv; int doit;{ struct stat statbuf; char dev[MAXPATHLEN]; if (*cp == 0) return 0; if (strncmp("/dev/", cp, 5) != 0) { 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 intsetxonxoff(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 intsetescape(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 voidprintescape(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. */voidtty_check_options(){ struct stat statbuf; int fdflags; if (demand && connect_script == 0) { 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", 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; struct stat statbuf; char numbuf[16]; /* * Get a pty master/slave pair if the pty, notty, socket, * or record options were specified. */ error("connect_tty"); 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) return -1; 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; 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, prio; prio = privopen? OPRIO_ROOT: tty_options[0].priority; if (prio < OPRIO_ROOT) seteuid(uid); error("devname=%s",devnam); ttyfd = open(devnam, O_NONBLOCK | O_RDWR, 0); err = errno; if (prio < OPRIO_ROOT) seteuid(0); if (ttyfd >= 0) break; errno = err; if (err != EINTR) { error("Failed to open %s: %m", devnam); status = EXIT_OPEN_FAILED; } if (!persist || err != EINTR) return -1; } real_ttyfd = 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"); /* * 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; /* * 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"); 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) return -1; } else { if (device_script(ptycommand, pty_master, pty_master, 1) < 0) return -1; ttyfd = pty_slave; close(pty_master); pty_master = -1; } } else if (pty_socket != NULL) { int fd = open_socket(pty_socket); if (fd < 0) return -1;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -