]> git.ozlabs.org Git - ppp.git/blob - pppd/tty.c
Move the tty-related stuff out to tty.c as far as possible.
[ppp.git] / pppd / tty.c
1 /*
2  * tty.c - code for handling serial ports in pppd.
3  *
4  * Copyright (C) 2000 Paul Mackerras.
5  * All rights reserved.
6  *
7  * Portions Copyright (c) 1989 Carnegie Mellon University.
8  * All rights reserved.
9  *
10  * Redistribution and use in source and binary forms are permitted
11  * provided that the above copyright notice and this paragraph are
12  * duplicated in all such forms and that any documentation,
13  * advertising materials, and other materials related to such
14  * distribution and use acknowledge that the software was developed
15  * by Carnegie Mellon University.  The name of the
16  * University may not be used to endorse or promote products derived
17  * from this software without specific prior written permission.
18  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
19  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
20  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
21  */
22
23 #define RCSID   "$Id: tty.c,v 1.1 2000/06/30 04:54:23 paulus Exp $"
24
25 #include <stdio.h>
26 #include <ctype.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
30 #include <signal.h>
31 #include <errno.h>
32 #include <fcntl.h>
33 #include <syslog.h>
34 #include <netdb.h>
35 #include <utmp.h>
36 #include <pwd.h>
37 #include <setjmp.h>
38 #include <sys/param.h>
39 #include <sys/types.h>
40 #include <sys/wait.h>
41 #include <sys/time.h>
42 #include <sys/resource.h>
43 #include <sys/stat.h>
44 #include <sys/socket.h>
45 #include <netinet/in.h>
46 #include <arpa/inet.h>
47
48 #include "pppd.h"
49 #include "fsm.h"
50 #include "lcp.h"
51
52 static int setxonxoff __P((char **));
53 static void finish_tty __P((void));
54 static int start_charshunt __P((int, int));
55 static void stop_charshunt __P((void *, int));
56 static void charshunt_done __P((void *));
57 static void charshunt __P((int, int, char *));
58 static int record_write __P((FILE *, int code, u_char *buf, int nb,
59                              struct timeval *));
60 static int open_socket __P((char *));
61 static void maybe_relock __P((void *, int));
62
63 static int pty_master;          /* fd for master side of pty */
64 static int pty_slave;           /* fd for slave side of pty */
65 static int real_ttyfd;          /* fd for actual serial port (not pty) */
66 static int ttyfd;               /* Serial port file descriptor */
67
68 mode_t tty_mode = (mode_t)-1;   /* Original access permissions to tty */
69 int baud_rate;                  /* Actual bits/second for serial device */
70 char *callback_script;          /* script for doing callback */
71 int charshunt_pid;              /* Process ID for charshunt */
72 int locked;                     /* lock() has succeeded */
73
74 /* option variables */
75 int     crtscts = 0;            /* Use hardware flow control */
76 bool    modem = 1;              /* Use modem control lines */
77 int     inspeed = 0;            /* Input/Output speed requested */
78 bool    lockflag = 0;           /* Create lock file to lock the serial dev */
79 char    *initializer = NULL;    /* Script to initialize physical link */
80 char    *connect_script = NULL; /* Script to establish physical link */
81 char    *disconnect_script = NULL; /* Script to disestablish physical link */
82 char    *welcomer = NULL;       /* Script to run after phys link estab. */
83 char    *ptycommand = NULL;     /* Command to run on other side of pty */
84 bool    notty = 0;              /* Stdin/out is not a tty */
85 char    *record_file = NULL;    /* File to record chars sent/received */
86 int     max_data_rate;          /* max bytes/sec through charshunt */
87 bool    sync_serial = 0;        /* Device is synchronous serial device */
88 char    *pty_socket = NULL;     /* Socket to connect to pty */
89 int     using_pty = 0;
90
91 extern uid_t uid;
92 extern int kill_link;
93
94 /* XXX */
95 extern int privopen;            /* don't lock, open device as root */
96
97 /* option descriptors */
98 option_t tty_options[] = {
99     { "lock", o_bool, &lockflag,
100       "Lock serial device with UUCP-style lock file", 1 },
101     { "nolock", o_bool, &lockflag,
102       "Don't lock serial device", OPT_PRIV },
103     { "init", o_string, &initializer,
104       "A program to initialize the device",
105       OPT_A2INFO | OPT_PRIVFIX, &initializer_info },
106     { "connect", o_string, &connect_script,
107       "A program to set up a connection",
108       OPT_A2INFO | OPT_PRIVFIX, &connect_script_info },
109     { "disconnect", o_string, &disconnect_script,
110       "Program to disconnect serial device",
111       OPT_A2INFO | OPT_PRIVFIX, &disconnect_script_info },
112     { "welcome", o_string, &welcomer,
113       "Script to welcome client",
114       OPT_A2INFO | OPT_PRIVFIX, &welcomer_info },
115     { "pty", o_string, &ptycommand,
116       "Script to run on pseudo-tty master side",
117       OPT_A2INFO | OPT_PRIVFIX | OPT_DEVNAM, &ptycommand_info },
118     { "notty", o_bool, &notty,
119       "Input/output is not a tty", OPT_DEVNAM | 1 },
120     { "socket", o_string, &pty_socket,
121       "Send and receive over socket, arg is host:port", OPT_DEVNAM },
122     { "record", o_string, &record_file,
123       "Record characters sent/received to file" },
124     { "crtscts", o_int, &crtscts,
125       "Set hardware (RTS/CTS) flow control", OPT_NOARG|OPT_VAL(1) },
126     { "nocrtscts", o_int, &crtscts,
127       "Disable hardware flow control", OPT_NOARG|OPT_VAL(-1) },
128     { "-crtscts", o_int, &crtscts,
129       "Disable hardware flow control", OPT_NOARG|OPT_VAL(-1) },
130     { "cdtrcts", o_int, &crtscts,
131       "Set alternate hardware (DTR/CTS) flow control", OPT_NOARG|OPT_VAL(2) },
132     { "nocdtrcts", o_int, &crtscts,
133       "Disable hardware flow control", OPT_NOARG|OPT_VAL(-1) },
134     { "xonxoff", o_special_noarg, (void *)setxonxoff,
135       "Set software (XON/XOFF) flow control" },
136     { "modem", o_bool, &modem,
137       "Use modem control lines", 1 },
138     { "local", o_bool, &modem,
139       "Don't use modem control lines" },
140     { "sync", o_bool, &sync_serial,
141       "Use synchronous HDLC serial encoding", 1 },
142     { "datarate", o_int, &max_data_rate,
143       "Maximum data rate in bytes/sec (with pty, notty or record option)" },
144     { NULL }
145 };
146
147 static int
148 setxonxoff(argv)
149     char **argv;
150 {
151         lcp_wantoptions[0].asyncmap |= 0x000A0000;      /* escape ^S and ^Q */
152         lcp_wantoptions[0].neg_asyncmap = 1;
153
154         crtscts = -2;
155         return 1;
156 }
157
158 /*
159  * tty_init - do various tty-related initializations.
160  */
161 void tty_init()
162 {
163     add_notifier(&pidchange, maybe_relock, 0);
164     add_options(tty_options);
165 }
166
167 /*
168  * connect_tty - get the serial port ready to start doing PPP.
169  * That is, open the serial port, set its speed and mode, and run
170  * the connector and/or welcomer.
171  */
172 int connect_tty()
173 {
174         char *connector;
175         int fdflags;
176         struct stat statbuf;
177         char numbuf[16];
178
179         /*
180          * Get a pty master/slave pair if the pty, notty, socket,
181          * or record options were specified.
182          */
183         strlcpy(ppp_devnam, devnam, sizeof(ppp_devnam));
184         pty_master = -1;
185         pty_slave = -1;
186         real_ttyfd = -1;
187         if (using_pty || record_file != NULL) {
188                 if (!get_pty(&pty_master, &pty_slave, ppp_devnam, uid)) {
189                         error("Couldn't allocate pseudo-tty");
190                         status = EXIT_FATAL_ERROR;
191                         return -1;
192                 }
193                 set_up_tty(pty_slave, 1);
194         }
195
196         /*
197          * Lock the device if we've been asked to.
198          */
199         status = EXIT_LOCK_FAILED;
200         if (lockflag && !privopen) {
201                 if (lock(devnam) < 0)
202                         return -1;
203                 locked = 1;
204         }
205
206         /*
207          * Open the serial device and set it up to be the ppp interface.
208          * First we open it in non-blocking mode so we can set the
209          * various termios flags appropriately.  If we aren't dialling
210          * out and we want to use the modem lines, we reopen it later
211          * in order to wait for the carrier detect signal from the modem.
212          */
213         hungup = 0;
214         kill_link = 0;
215         connector = doing_callback? callback_script: connect_script;
216         if (devnam[0] != 0) {
217                 for (;;) {
218                         /* If the user specified the device name, become the
219                            user before opening it. */
220                         int err;
221                         if (!devnam_info.priv && !privopen)
222                                 seteuid(uid);
223                         ttyfd = open(devnam, O_NONBLOCK | O_RDWR, 0);
224                         err = errno;
225                         if (!devnam_info.priv && !privopen)
226                                 seteuid(0);
227                         if (ttyfd >= 0)
228                                 break;
229                         errno = err;
230                         if (err != EINTR) {
231                                 error("Failed to open %s: %m", devnam);
232                                 status = EXIT_OPEN_FAILED;
233                         }
234                         if (!persist || err != EINTR)
235                                 return -1;
236                 }
237                 if ((fdflags = fcntl(ttyfd, F_GETFL)) == -1
238                     || fcntl(ttyfd, F_SETFL, fdflags & ~O_NONBLOCK) < 0)
239                         warn("Couldn't reset non-blocking mode on device: %m");
240
241                 /*
242                  * Do the equivalent of `mesg n' to stop broadcast messages.
243                  */
244                 if (fstat(ttyfd, &statbuf) < 0
245                     || fchmod(ttyfd, statbuf.st_mode & ~(S_IWGRP | S_IWOTH)) < 0) {
246                         warn("Couldn't restrict write permissions to %s: %m", devnam);
247                 } else
248                         tty_mode = statbuf.st_mode;
249
250                 /*
251                  * Set line speed, flow control, etc.
252                  * If we have a non-null connection or initializer script,
253                  * on most systems we set CLOCAL for now so that we can talk
254                  * to the modem before carrier comes up.  But this has the
255                  * side effect that we might miss it if CD drops before we
256                  * get to clear CLOCAL below.  On systems where we can talk
257                  * successfully to the modem with CLOCAL clear and CD down,
258                  * we could clear CLOCAL at this point.
259                  */
260                 set_up_tty(ttyfd, ((connector != NULL && connector[0] != 0)
261                                    || initializer != NULL));
262                 real_ttyfd = ttyfd;
263         }
264
265         /*
266          * If the pty, socket, notty and/or record option was specified,
267          * start up the character shunt now.
268          */
269         status = EXIT_PTYCMD_FAILED;
270         if (ptycommand != NULL) {
271                 if (record_file != NULL) {
272                         int ipipe[2], opipe[2], ok;
273
274                         if (pipe(ipipe) < 0 || pipe(opipe) < 0)
275                                 fatal("Couldn't create pipes for record option: %m");
276                         ok = device_script(ptycommand, opipe[0], ipipe[1], 1) == 0
277                                 && start_charshunt(ipipe[0], opipe[1]);
278                         close(ipipe[0]);
279                         close(ipipe[1]);
280                         close(opipe[0]);
281                         close(opipe[1]);
282                         if (!ok)
283                                 return -1;
284                 } else {
285                         if (device_script(ptycommand, pty_master, pty_master, 1) < 0)
286                                 return -1;
287                         ttyfd = pty_slave;
288                         close(pty_master);
289                         pty_master = -1;
290                 }
291         } else if (pty_socket != NULL) {
292                 int fd = open_socket(pty_socket);
293                 if (fd < 0)
294                         return -1;
295                 if (!start_charshunt(fd, fd))
296                         return -1;
297         } else if (notty) {
298                 if (!start_charshunt(0, 1))
299                         return -1;
300         } else if (record_file != NULL) {
301                 if (!start_charshunt(ttyfd, ttyfd))
302                         return -1;
303         }
304
305         /* run connection script */
306         if ((connector && connector[0]) || initializer) {
307                 if (real_ttyfd != -1) {
308                         /* XXX do this if doing_callback == CALLBACK_DIALIN? */
309                         if (!default_device && modem) {
310                                 setdtr(real_ttyfd, 0);  /* in case modem is off hook */
311                                 sleep(1);
312                                 setdtr(real_ttyfd, 1);
313                         }
314                 }
315
316                 if (initializer && initializer[0]) {
317                         if (device_script(initializer, ttyfd, ttyfd, 0) < 0) {
318                                 error("Initializer script failed");
319                                 status = EXIT_INIT_FAILED;
320                                 return -1;
321                         }
322                         if (kill_link) {
323                                 disconnect_tty();
324                                 return -1;
325                         }
326                         info("Serial port initialized.");
327                 }
328
329                 if (connector && connector[0]) {
330                         if (device_script(connector, ttyfd, ttyfd, 0) < 0) {
331                                 error("Connect script failed");
332                                 status = EXIT_CONNECT_FAILED;
333                                 return -1;
334                         }
335                         if (kill_link) {
336                                 disconnect_tty();
337                                 return -1;
338                         }
339                         info("Serial connection established.");
340                 }
341
342                 /* set line speed, flow control, etc.;
343                    clear CLOCAL if modem option */
344                 if (real_ttyfd != -1)
345                         set_up_tty(real_ttyfd, 0);
346
347                 if (doing_callback == CALLBACK_DIALIN)
348                         connector = NULL;
349         }
350
351         /* reopen tty if necessary to wait for carrier */
352         if (connector == NULL && modem && devnam[0] != 0) {
353                 int i;
354                 for (;;) {
355                         if ((i = open(devnam, O_RDWR)) >= 0)
356                                 break;
357                         if (errno != EINTR) {
358                                 error("Failed to reopen %s: %m", devnam);
359                                 status = EXIT_OPEN_FAILED;
360                         }
361                         if (!persist || errno != EINTR || hungup || kill_link)
362                                 return -1;
363                 }
364                 close(i);
365         }
366
367         slprintf(numbuf, sizeof(numbuf), "%d", baud_rate);
368         script_setenv("SPEED", numbuf, 0);
369
370         /* run welcome script, if any */
371         if (welcomer && welcomer[0]) {
372                 if (device_script(welcomer, ttyfd, ttyfd, 0) < 0)
373                         warn("Welcome script failed");
374         }
375
376         return ttyfd;
377 }
378
379
380 void disconnect_tty()
381 {
382         if (disconnect_script == NULL || hungup)
383                 return;
384         if (real_ttyfd >= 0)
385                 set_up_tty(real_ttyfd, 1);
386         if (device_script(disconnect_script, ttyfd, ttyfd, 0) < 0) {
387                 warn("disconnect script failed");
388         } else {
389                 info("Serial link disconnected.");
390         }
391 }
392
393 void tty_close_fds()
394 {
395         if (pty_master >= 0)
396                 close(pty_master);
397         if (pty_slave >= 0)
398                 close(pty_slave);
399         if (real_ttyfd >= 0) {
400                 close(real_ttyfd);
401                 real_ttyfd = -1;
402         }
403         /* N.B. ttyfd will == either pty_slave or real_ttyfd */
404 }
405
406 void cleanup_tty()
407 {
408         if (real_ttyfd >= 0)
409                 finish_tty();
410         tty_close_fds();
411         if (locked) {
412                 unlock();
413                 locked = 0;
414         }
415 }
416
417 /*
418  * finish_tty - restore the terminal device to its original settings
419  */
420 static void
421 finish_tty()
422 {
423         /* drop dtr to hang up */
424         if (!default_device && modem) {
425                 setdtr(real_ttyfd, 0);
426                 /*
427                  * This sleep is in case the serial port has CLOCAL set by default,
428                  * and consequently will reassert DTR when we close the device.
429                  */
430                 sleep(1);
431         }
432
433         restore_tty(real_ttyfd);
434
435         if (tty_mode != (mode_t) -1) {
436                 if (fchmod(real_ttyfd, tty_mode) != 0) {
437                         /* XXX if devnam is a symlink, this will change the link */
438                         chmod(devnam, tty_mode);
439                 }
440         }
441
442         close(real_ttyfd);
443         real_ttyfd = -1;
444 }
445
446 /*
447  * maybe_relock - our PID has changed, maybe update the lock file.
448  */
449 static void
450 maybe_relock(arg, pid)
451     void *arg;
452     int pid;
453 {
454     if (locked)
455         relock(pid);
456 }
457
458 /*
459  * open_socket - establish a stream socket connection to the nominated
460  * host and port.
461  */
462 static int
463 open_socket(dest)
464     char *dest;
465 {
466     char *sep, *endp = NULL;
467     int sock, port = -1;
468     u_int32_t host;
469     struct hostent *hent;
470     struct sockaddr_in sad;
471
472     /* parse host:port and resolve host to an IP address */
473     sep = strchr(dest, ':');
474     if (sep != NULL)
475         port = strtol(sep+1, &endp, 10);
476     if (port < 0 || endp == sep+1 || sep == dest) {
477         error("Can't parse host:port for socket destination");
478         return -1;
479     }
480     *sep = 0;
481     host = inet_addr(dest);
482     if (host == (u_int32_t) -1) {
483         hent = gethostbyname(dest);
484         if (hent == NULL) {
485             error("%s: unknown host in socket option", dest);
486             *sep = ':';
487             return -1;
488         }
489         host = *(u_int32_t *)(hent->h_addr_list[0]);
490     }
491     *sep = ':';
492
493     /* get a socket and connect it to the other end */
494     sock = socket(PF_INET, SOCK_STREAM, 0);
495     if (sock < 0) {
496         error("Can't create socket: %m");
497         return -1;
498     }
499     memset(&sad, 0, sizeof(sad));
500     sad.sin_family = AF_INET;
501     sad.sin_port = htons(port);
502     sad.sin_addr.s_addr = host;
503     if (connect(sock, (struct sockaddr *)&sad, sizeof(sad)) < 0) {
504         error("Can't connect to %s: %m", dest);
505         close(sock);
506         return -1;
507     }
508
509     return sock;
510 }
511
512
513 /*
514  * start_charshunt - create a child process to run the character shunt.
515  */
516 static int
517 start_charshunt(ifd, ofd)
518     int ifd, ofd;
519 {
520     int cpid;
521
522     cpid = fork();
523     if (cpid == -1) {
524         error("Can't fork process for character shunt: %m");
525         return 0;
526     }
527     if (cpid == 0) {
528         /* child */
529         close(pty_slave);
530         setuid(uid);
531         if (getuid() != uid)
532             fatal("setuid failed");
533         setgid(getgid());
534         if (!nodetach)
535             log_to_fd = -1;
536         charshunt(ifd, ofd, record_file);
537         exit(0);
538     }
539     charshunt_pid = cpid;
540     add_notifier(&sigreceived, stop_charshunt, 0);
541     close(pty_master);
542     pty_master = -1;
543     ttyfd = pty_slave;
544     record_child(cpid, "pppd (charshunt)", charshunt_done, NULL);
545     return 1;
546 }
547
548 static void
549 charshunt_done(arg)
550     void *arg;
551 {
552         charshunt_pid = 0;
553 }
554
555 static void
556 stop_charshunt(arg, sig)
557     void *arg;
558     int sig;
559 {
560         if (charshunt_pid)
561                 kill(charshunt_pid, (sig == SIGINT? sig: SIGTERM));
562 }
563
564 /*
565  * charshunt - the character shunt, which passes characters between
566  * the pty master side and the serial port (or stdin/stdout).
567  * This runs as the user (not as root).
568  * (We assume ofd >= ifd which is true the way this gets called. :-).
569  */
570 static void
571 charshunt(ifd, ofd, record_file)
572     int ifd, ofd;
573     char *record_file;
574 {
575     int n, nfds;
576     fd_set ready, writey;
577     u_char *ibufp, *obufp;
578     int nibuf, nobuf;
579     int flags;
580     int pty_readable, stdin_readable;
581     struct timeval lasttime;
582     FILE *recordf = NULL;
583     int ilevel, olevel, max_level;
584     struct timeval levelt, tout, *top;
585     extern u_char inpacket_buf[];
586
587     /*
588      * Reset signal handlers.
589      */
590     signal(SIGHUP, SIG_IGN);            /* Hangup */
591     signal(SIGINT, SIG_DFL);            /* Interrupt */
592     signal(SIGTERM, SIG_DFL);           /* Terminate */
593     signal(SIGCHLD, SIG_DFL);
594     signal(SIGUSR1, SIG_DFL);
595     signal(SIGUSR2, SIG_DFL);
596     signal(SIGABRT, SIG_DFL);
597     signal(SIGALRM, SIG_DFL);
598     signal(SIGFPE, SIG_DFL);
599     signal(SIGILL, SIG_DFL);
600     signal(SIGPIPE, SIG_DFL);
601     signal(SIGQUIT, SIG_DFL);
602     signal(SIGSEGV, SIG_DFL);
603 #ifdef SIGBUS
604     signal(SIGBUS, SIG_DFL);
605 #endif
606 #ifdef SIGEMT
607     signal(SIGEMT, SIG_DFL);
608 #endif
609 #ifdef SIGPOLL
610     signal(SIGPOLL, SIG_DFL);
611 #endif
612 #ifdef SIGPROF
613     signal(SIGPROF, SIG_DFL);
614 #endif
615 #ifdef SIGSYS
616     signal(SIGSYS, SIG_DFL);
617 #endif
618 #ifdef SIGTRAP
619     signal(SIGTRAP, SIG_DFL);
620 #endif
621 #ifdef SIGVTALRM
622     signal(SIGVTALRM, SIG_DFL);
623 #endif
624 #ifdef SIGXCPU
625     signal(SIGXCPU, SIG_DFL);
626 #endif
627 #ifdef SIGXFSZ
628     signal(SIGXFSZ, SIG_DFL);
629 #endif
630
631     /*
632      * Open the record file if required.
633      */
634     if (record_file != NULL) {
635         recordf = fopen(record_file, "a");
636         if (recordf == NULL)
637             error("Couldn't create record file %s: %m", record_file);
638     }
639
640     /* set all the fds to non-blocking mode */
641     flags = fcntl(pty_master, F_GETFL);
642     if (flags == -1
643         || fcntl(pty_master, F_SETFL, flags | O_NONBLOCK) == -1)
644         warn("couldn't set pty master to nonblock: %m");
645     flags = fcntl(ifd, F_GETFL);
646     if (flags == -1
647         || fcntl(ifd, F_SETFL, flags | O_NONBLOCK) == -1)
648         warn("couldn't set %s to nonblock: %m", (ifd==0? "stdin": "tty"));
649     if (ofd != ifd) {
650         flags = fcntl(ofd, F_GETFL);
651         if (flags == -1
652             || fcntl(ofd, F_SETFL, flags | O_NONBLOCK) == -1)
653             warn("couldn't set stdout to nonblock: %m");
654     }
655
656     nibuf = nobuf = 0;
657     ibufp = obufp = NULL;
658     pty_readable = stdin_readable = 1;
659
660     ilevel = olevel = 0;
661     gettimeofday(&levelt, NULL);
662     if (max_data_rate) {
663         max_level = max_data_rate / 10;
664         if (max_level < 100)
665             max_level = 100;
666     } else
667         max_level = PPP_MRU + PPP_HDRLEN + 1;
668
669     nfds = (ofd > pty_master? ofd: pty_master) + 1;
670     if (recordf != NULL) {
671         gettimeofday(&lasttime, NULL);
672         putc(7, recordf);       /* put start marker */
673         putc(lasttime.tv_sec >> 24, recordf);
674         putc(lasttime.tv_sec >> 16, recordf);
675         putc(lasttime.tv_sec >> 8, recordf);
676         putc(lasttime.tv_sec, recordf);
677         lasttime.tv_usec = 0;
678     }
679
680     while (nibuf != 0 || nobuf != 0 || pty_readable || stdin_readable) {
681         top = 0;
682         tout.tv_sec = 0;
683         tout.tv_usec = 10000;
684         FD_ZERO(&ready);
685         FD_ZERO(&writey);
686         if (nibuf != 0) {
687             if (ilevel >= max_level)
688                 top = &tout;
689             else
690                 FD_SET(pty_master, &writey);
691         } else if (stdin_readable)
692             FD_SET(ifd, &ready);
693         if (nobuf != 0) {
694             if (olevel >= max_level)
695                 top = &tout;
696             else
697                 FD_SET(ofd, &writey);
698         } else if (pty_readable)
699             FD_SET(pty_master, &ready);
700         if (select(nfds, &ready, &writey, NULL, top) < 0) {
701             if (errno != EINTR)
702                 fatal("select");
703             continue;
704         }
705         if (max_data_rate) {
706             double dt;
707             int nbt;
708             struct timeval now;
709
710             gettimeofday(&now, NULL);
711             dt = (now.tv_sec - levelt.tv_sec
712                   + (now.tv_usec - levelt.tv_usec) / 1e6);
713             nbt = (int)(dt * max_data_rate);
714             ilevel = (nbt < 0 || nbt > ilevel)? 0: ilevel - nbt;
715             olevel = (nbt < 0 || nbt > olevel)? 0: olevel - nbt;
716             levelt = now;
717         } else
718             ilevel = olevel = 0;
719         if (FD_ISSET(ifd, &ready)) {
720             ibufp = inpacket_buf;
721             nibuf = read(ifd, ibufp, PPP_MRU + PPP_HDRLEN);
722             if (nibuf < 0 && errno == EIO)
723                 nibuf = 0;
724             if (nibuf < 0) {
725                 if (!(errno == EINTR || errno == EAGAIN)) {
726                     error("Error reading standard input: %m");
727                     break;
728                 }
729                 nibuf = 0;
730             } else if (nibuf == 0) {
731                 /* end of file from stdin */
732                 stdin_readable = 0;
733                 /* do a 0-length write, hopefully this will generate
734                    an EOF (hangup) on the slave side. */
735                 write(pty_master, inpacket_buf, 0);
736                 if (recordf)
737                     if (!record_write(recordf, 4, NULL, 0, &lasttime))
738                         recordf = NULL;
739             } else {
740                 FD_SET(pty_master, &writey);
741                 if (recordf)
742                     if (!record_write(recordf, 2, ibufp, nibuf, &lasttime))
743                         recordf = NULL;
744             }
745         }
746         if (FD_ISSET(pty_master, &ready)) {
747             obufp = outpacket_buf;
748             nobuf = read(pty_master, obufp, PPP_MRU + PPP_HDRLEN);
749             if (nobuf < 0 && errno == EIO)
750                 nobuf = 0;
751             if (nobuf < 0) {
752                 if (!(errno == EINTR || errno == EAGAIN)) {
753                     error("Error reading pseudo-tty master: %m");
754                     break;
755                 }
756                 nobuf = 0;
757             } else if (nobuf == 0) {
758                 /* end of file from the pty - slave side has closed */
759                 pty_readable = 0;
760                 stdin_readable = 0;     /* pty is not writable now */
761                 nibuf = 0;
762                 close(ofd);
763                 if (recordf)
764                     if (!record_write(recordf, 3, NULL, 0, &lasttime))
765                         recordf = NULL;
766             } else {
767                 FD_SET(ofd, &writey);
768                 if (recordf)
769                     if (!record_write(recordf, 1, obufp, nobuf, &lasttime))
770                         recordf = NULL;
771             }
772         }
773         if (FD_ISSET(ofd, &writey)) {
774             n = nobuf;
775             if (olevel + n > max_level)
776                 n = max_level - olevel;
777             n = write(ofd, obufp, n);
778             if (n < 0) {
779                 if (errno == EIO) {
780                     pty_readable = 0;
781                     nobuf = 0;
782                 } else if (errno != EAGAIN && errno != EINTR) {
783                     error("Error writing standard output: %m");
784                     break;
785                 }
786             } else {
787                 obufp += n;
788                 nobuf -= n;
789                 olevel += n;
790             }
791         }
792         if (FD_ISSET(pty_master, &writey)) {
793             n = nibuf;
794             if (ilevel + n > max_level)
795                 n = max_level - ilevel;
796             n = write(pty_master, ibufp, n);
797             if (n < 0) {
798                 if (errno == EIO) {
799                     stdin_readable = 0;
800                     nibuf = 0;
801                 } else if (errno != EAGAIN && errno != EINTR) {
802                     error("Error writing pseudo-tty master: %m");
803                     break;
804                 }
805             } else {
806                 ibufp += n;
807                 nibuf -= n;
808                 ilevel += n;
809             }
810         }
811     }
812     exit(0);
813 }
814
815 static int
816 record_write(f, code, buf, nb, tp)
817     FILE *f;
818     int code;
819     u_char *buf;
820     int nb;
821     struct timeval *tp;
822 {
823     struct timeval now;
824     int diff;
825
826     gettimeofday(&now, NULL);
827     now.tv_usec /= 100000;      /* actually 1/10 s, not usec now */
828     diff = (now.tv_sec - tp->tv_sec) * 10 + (now.tv_usec - tp->tv_usec);
829     if (diff > 0) {
830         if (diff > 255) {
831             putc(5, f);
832             putc(diff >> 24, f);
833             putc(diff >> 16, f);
834             putc(diff >> 8, f);
835             putc(diff, f);
836         } else {
837             putc(6, f);
838             putc(diff, f);
839         }
840         *tp = now;
841     }
842     putc(code, f);
843     if (buf != NULL) {
844         putc(nb >> 8, f);
845         putc(nb, f);
846         fwrite(buf, nb, 1, f);
847     }
848     fflush(f);
849     if (ferror(f)) {
850         error("Error writing record file: %m");
851         return 0;
852     }
853     return 1;
854 }