pppctl.c revision 73558
11553Srgrimes/*-
21553Srgrimes * Copyright (c) 1997 Brian Somers <brian@Awfulhak.org>
31553Srgrimes * All rights reserved.
41553Srgrimes *
51553Srgrimes * Redistribution and use in source and binary forms, with or without
61553Srgrimes * modification, are permitted provided that the following conditions
71553Srgrimes * are met:
81553Srgrimes * 1. Redistributions of source code must retain the above copyright
91553Srgrimes *    notice, this list of conditions and the following disclaimer.
101553Srgrimes * 2. Redistributions in binary form must reproduce the above copyright
111553Srgrimes *    notice, this list of conditions and the following disclaimer in the
121553Srgrimes *    documentation and/or other materials provided with the distribution.
13121300Sphk *
141553Srgrimes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
151553Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
161553Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
171553Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
181553Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
191553Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
201553Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
211553Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
221553Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
231553Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
241553Srgrimes * SUCH DAMAGE.
251553Srgrimes *
261553Srgrimes * $FreeBSD: head/usr.sbin/pppctl/pppctl.c 73558 2001-03-05 00:59:53Z brian $
271553Srgrimes */
281553Srgrimes
291553Srgrimes#include <sys/types.h>
30114601Sobrien
311553Srgrimes#include <sys/socket.h>
321553Srgrimes#include <netinet/in.h>
33114601Sobrien#include <arpa/inet.h>
3430027Scharnier#include <sys/un.h>
35114601Sobrien#include <netdb.h>
36114601Sobrien
371553Srgrimes#include <sys/time.h>
381553Srgrimes#include <err.h>
391553Srgrimes#include <errno.h>
401553Srgrimes#include <histedit.h>
4130027Scharnier#include <semaphore.h>
4230027Scharnier#include <pthread.h>
431553Srgrimes#include <setjmp.h>
441553Srgrimes#include <signal.h>
4530027Scharnier#include <stdio.h>
461553Srgrimes#include <stdlib.h>
471553Srgrimes#include <string.h>
481553Srgrimes#include <time.h>
491553Srgrimes#include <unistd.h>
501553Srgrimes
511553Srgrimes#define LINELEN 2048
521553Srgrimes
5399800Salfred/* Data passed to the threads we create */
5499800Salfredstruct thread_data {
551553Srgrimes    EditLine *edit;		/* libedit stuff */
561553Srgrimes    History *hist;		/* libedit stuff */
57122135Sphk    pthread_t trm;		/* Terminal thread (for pthread_kill()) */
581553Srgrimes    int ppp;			/* ppp descriptor */
591553Srgrimes};
601553Srgrimes
61122135Sphk/* Flags passed to Receive() */
621553Srgrimes#define REC_PASSWD  (1)		/* Handle a password request from ppp */
631553Srgrimes#define REC_SHOW    (2)		/* Show everything except prompts */
641553Srgrimes#define REC_VERBOSE (4)		/* Show everything */
651553Srgrimes
661553Srgrimesstatic char *passwd;
671553Srgrimesstatic char *prompt;		/* Tell libedit what the current prompt is */
68174403Sdesstatic int data = -1;		/* setjmp() has been done when data != -1 */
69174403Sdesstatic jmp_buf pppdead;		/* Jump the Terminal thread out of el_gets() */
70174403Sdesstatic int timetogo;		/* Tell the Monitor thread to exit */
71174403Sdesstatic sem_t sem_select;	/* select() co-ordination between threads */
72174403Sdesstatic int TimedOut;		/* Set if our connect() timed out */
73174403Sdesstatic int want_sem_post;	/* Need to let the Monitor thread in ? */
74121299Sphk
751553Srgrimes/*
76121299Sphk * How to use pppctl...
77121299Sphk */
78121299Sphkstatic int
799675Sbdeusage()
801553Srgrimes{
8199802Salfred    fprintf(stderr, "usage: pppctl [-v] [-t n] [-p passwd] "
821553Srgrimes            "Port|LocalSock [command[;command]...]\n");
8399802Salfred    fprintf(stderr, "              -v tells pppctl to output all"
841553Srgrimes            " conversation\n");
85174403Sdes    fprintf(stderr, "              -t n specifies a timeout of n"
8630027Scharnier            " seconds when connecting (default 2)\n");
871553Srgrimes    fprintf(stderr, "              -p passwd specifies your password\n");
889675Sbde    exit(1);
892860Srgrimes}
9060418Swollman
9160418Swollman/*
9260418Swollman * Handle the SIGALRM received due to a connect() timeout.
9360418Swollman */
941553Srgrimesstatic void
951553SrgrimesTimeout(int Sig)
9663086Sjoe{
971553Srgrimes    TimedOut = 1;
981553Srgrimes}
999675Sbde
1001553Srgrimes/*
1018857Srgrimes * A callback routine for libedit to find out what the current prompt is.
1021553Srgrimes * All the work is done in Receive() below.
1031553Srgrimes */
1041553Srgrimesstatic char *
1051553SrgrimesGetPrompt(EditLine *e)
1061553Srgrimes{
1071553Srgrimes    if (prompt == NULL)
10830027Scharnier        prompt = "";
1091553Srgrimes    return prompt;
1101553Srgrimes}
1111553Srgrimes
1121553Srgrimes/*
1131553Srgrimes * Receive data from the ppp descriptor.
1141553Srgrimes * We also handle password prompts here (if asked via the `display' arg)
1159675Sbde * and buffer what our prompt looks like (via the `prompt' global).
1169675Sbde */
1171553Srgrimesstatic int
1182860SrgrimesReceive(int fd, int display)
1192860Srgrimes{
1201553Srgrimes    static char Buffer[LINELEN];
1211553Srgrimes    struct timeval t;
12236670Speter    int Result;
12336670Speter    char *last;
1241553Srgrimes    fd_set f;
12536841Speter    int len;
1261553Srgrimes
1271553Srgrimes    FD_ZERO(&f);
1281553Srgrimes    FD_SET(fd, &f);
1291553Srgrimes    t.tv_sec = 0;
1301553Srgrimes    t.tv_usec = 100000;
1311553Srgrimes    prompt = Buffer;
1321553Srgrimes    len = 0;
1331553Srgrimes
1341553Srgrimes    while (Result = read(fd, Buffer+len, sizeof(Buffer)-len-1), Result != -1) {
1351553Srgrimes        if (Result == 0 && errno != EINTR) {
1361553Srgrimes            Result = -1;
1379675Sbde            break;
1381553Srgrimes        }
13966584Sphk        len += Result;
1401553Srgrimes        Buffer[len] = '\0';
1419675Sbde        if (len > 2 && !strcmp(Buffer+len-2, "> ")) {
1429675Sbde            prompt = strrchr(Buffer, '\n');
1431553Srgrimes            if (display & (REC_SHOW|REC_VERBOSE)) {
1441553Srgrimes                if (display & REC_VERBOSE)
1451553Srgrimes                    last = Buffer+len-1;
1461553Srgrimes                else
1471553Srgrimes                    last = prompt;
1481553Srgrimes                if (last) {
1491553Srgrimes                    last++;
1501553Srgrimes                    write(1, Buffer, last-Buffer);
1511553Srgrimes                }
1521553Srgrimes            }
1531553Srgrimes            prompt = prompt == NULL ? Buffer : prompt+1;
154112214Srobert            for (last = Buffer+len-2; last > Buffer && *last != ' '; last--)
1551553Srgrimes                ;
1561553Srgrimes            if (last > Buffer+3 && !strncmp(last-3, " on", 3)) {
1571553Srgrimes                 /* a password is required ! */
1581553Srgrimes                 if (display & REC_PASSWD) {
159121299Sphk                    /* password time */
1601553Srgrimes                    if (!passwd)
161121299Sphk                        passwd = getpass("Password: ");
162121299Sphk                    sprintf(Buffer, "passwd %s\n", passwd);
163148966Sbrian                    memset(passwd, '\0', strlen(passwd));
164148966Sbrian                    if (display & REC_VERBOSE)
1651553Srgrimes                        write(1, Buffer, strlen(Buffer));
1661553Srgrimes                    write(fd, Buffer, strlen(Buffer));
167160083Smaxim                    memset(Buffer, '\0', strlen(Buffer));
168160083Smaxim                    return Receive(fd, display & ~REC_PASSWD);
1691553Srgrimes                }
1701553Srgrimes                Result = 1;
1711553Srgrimes            } else
17263853Simp                Result = 0;
17363853Simp            break;
17463853Simp        } else
17563853Simp            prompt = "";
176121300Sphk        if (len == sizeof Buffer - 1) {
17763853Simp            int flush;
17863853Simp            if ((last = strrchr(Buffer, '\n')) == NULL)
17963853Simp                /* Yeuch - this is one mother of a line ! */
18066584Sphk                flush = sizeof Buffer / 2;
18163853Simp            else
18263087Sjoe                flush = last - Buffer + 1;
1831553Srgrimes            write(1, Buffer, flush);
1841553Srgrimes            strcpy(Buffer, Buffer + flush);
1851553Srgrimes            len -= flush;
1861553Srgrimes        }
1871553Srgrimes        if ((Result = select(fd + 1, &f, NULL, NULL, &t)) <= 0) {
18863087Sjoe            if (len)
18963087Sjoe                write(1, Buffer, len);
19063087Sjoe            break;
19163087Sjoe        }
19251705Sbillf    }
1931553Srgrimes
19463087Sjoe    return Result;
1951553Srgrimes}
19663087Sjoe
19763087Sjoe/*
19863087Sjoe * Handle being told by the Monitor thread that there's data to be read
19963087Sjoe * on the ppp descriptor.
20063087Sjoe *
20163087Sjoe * Note, this is a signal handler - be careful of what we do !
20263087Sjoe */
203148966Sbrianstatic void
204148966SbrianInputHandler(int sig)
205148966Sbrian{
206148966Sbrian    static char buf[LINELEN];
207148966Sbrian    struct timeval t;
208148966Sbrian    int len;
209148966Sbrian    fd_set f;
210148966Sbrian
211148966Sbrian    if (data != -1) {
212148966Sbrian        FD_ZERO(&f);
213148966Sbrian        FD_SET(data, &f);
214148966Sbrian        t.tv_sec = t.tv_usec = 0;
215148966Sbrian
216148966Sbrian        if (select(data + 1, &f, NULL, NULL, &t) > 0) {
21763087Sjoe            len = read(data, buf, sizeof buf);
21863087Sjoe
21936670Speter            if (len > 0)
2201553Srgrimes                write(1, buf, len);
22136670Speter            else if (data != -1)
2221553Srgrimes                longjmp(pppdead, -1);
2231553Srgrimes        }
2241553Srgrimes
2251553Srgrimes        sem_post(&sem_select);
2261553Srgrimes    } else
22751705Sbillf        /* Don't let the Monitor thread in 'till we've set ``data'' up again */
2281553Srgrimes        want_sem_post = 1;
2291553Srgrimes}
2301553Srgrimes
2311553Srgrimes/*
2321553Srgrimes * This is a simple wrapper for el_gets(), allowing our SIGUSR1 signal
2331553Srgrimes * handler (above) to take effect only after we've done a setjmp().
2341553Srgrimes *
2351553Srgrimes * We don't want it to do anything outside of here as we're going to
2361553Srgrimes * service the ppp descriptor anyway.
2371553Srgrimes */
238148966Sbrianstatic const char *
239148966SbrianSmartGets(EditLine *e, int *count, int fd)
240148966Sbrian{
241148966Sbrian    const char *result;
242148966Sbrian
243148966Sbrian    if (setjmp(pppdead))
244148966Sbrian        result = NULL;
245148966Sbrian    else {
246148966Sbrian        data = fd;
247148966Sbrian        if (want_sem_post)
248148966Sbrian            /* Let the Monitor thread in again */
249148966Sbrian            sem_post(&sem_select);
2501553Srgrimes        result = el_gets(e, count);
2511553Srgrimes    }
2521553Srgrimes
2531553Srgrimes    data = -1;
25454375Sjoe
25554375Sjoe    return result;
25654375Sjoe}
25754375Sjoe
2581553Srgrimes/*
2591553Srgrimes * The Terminal thread entry point.
260 *
261 * The bulk of the interactive work is done here.  We read the terminal,
262 * write the results to our ppp descriptor and read the results back.
263 *
264 * While reading the terminal (using el_gets()), it's possible to take
265 * a SIGUSR1 from the Monitor thread, telling us that the ppp descriptor
266 * has some data.  The data is read and displayed by the signal handler
267 * itself.
268 */
269static void *
270Terminal(void *v)
271{
272    struct sigaction act, oact;
273    struct thread_data *td;
274    const char *l;
275    int len;
276#ifdef __NetBSD__
277    HistEvent hev = { 0, "" };
278#endif
279
280    act.sa_handler = InputHandler;
281    sigemptyset(&act.sa_mask);
282    act.sa_flags = SA_RESTART;
283    sigaction(SIGUSR1, &act, &oact);
284
285    td = (struct thread_data *)v;
286    want_sem_post = 1;
287
288    while ((l = SmartGets(td->edit, &len, td->ppp))) {
289        if (len > 1)
290#ifdef __NetBSD__
291            history(td->hist, &hev, H_ENTER, l);
292#else
293            history(td->hist, H_ENTER, l);
294#endif
295        write(td->ppp, l, len);
296        if (Receive(td->ppp, REC_SHOW) != 0)
297            break;
298    }
299
300    return NULL;
301}
302
303/*
304 * The Monitor thread entry point.
305 *
306 * This thread simply monitors our ppp descriptor.  When there's something
307 * to read, a SIGUSR1 is sent to the Terminal thread.
308 *
309 * sem_select() is used by the Terminal thread to keep us from sending
310 * flurries of SIGUSR1s, and is used from the main thread to wake us up
311 * when it's time to exit.
312 */
313static void *
314Monitor(void *v)
315{
316    struct thread_data *td;
317    fd_set f;
318    int ret;
319
320    td = (struct thread_data *)v;
321    FD_ZERO(&f);
322    FD_SET(td->ppp, &f);
323
324    sem_wait(&sem_select);
325    while (!timetogo)
326        if ((ret = select(td->ppp + 1, &f, NULL, NULL, NULL)) > 0) {
327            pthread_kill(td->trm, SIGUSR1);
328            sem_wait(&sem_select);
329        }
330
331    return NULL;
332}
333
334/*
335 * Connect to ppp using either a local domain socket or a tcp socket.
336 *
337 * If we're given arguments, process them and quit, otherwise create two
338 * threads to handle interactive mode.
339 */
340int
341main(int argc, char **argv)
342{
343    struct servent *s;
344    struct hostent *h;
345    struct sockaddr *sock;
346    struct sockaddr_in ifsin;
347    struct sockaddr_un ifsun;
348    int socksz, arg, fd, len, verbose, save_errno, hide1, hide1off, hide2;
349    unsigned TimeoutVal;
350    char *DoneWord = "x", *next, *start;
351    struct sigaction act, oact;
352    void *thread_ret;
353    pthread_t mon;
354    char Command[LINELEN];
355    char Buffer[LINELEN];
356
357    verbose = 0;
358    TimeoutVal = 2;
359    hide1 = hide1off = hide2 = 0;
360
361    for (arg = 1; arg < argc; arg++)
362        if (*argv[arg] == '-') {
363            for (start = argv[arg] + 1; *start; start++)
364                switch (*start) {
365                    case 't':
366                        TimeoutVal = (unsigned)atoi
367                            (start[1] ? start + 1 : argv[++arg]);
368                        start = DoneWord;
369                        break;
370
371                    case 'v':
372                        verbose = REC_VERBOSE;
373                        break;
374
375                    case 'p':
376                        if (start[1]) {
377                          hide1 = arg;
378                          hide1off = start - argv[arg];
379                          passwd = start + 1;
380                        } else {
381                          hide1 = arg;
382                          hide1off = start - argv[arg];
383                          passwd = argv[++arg];
384                          hide2 = arg;
385                        }
386                        start = DoneWord;
387                        break;
388
389                    default:
390                        usage();
391                }
392        }
393        else
394            break;
395
396
397    if (argc < arg + 1)
398        usage();
399
400    if (hide1) {
401      char title[1024];
402      int pos, harg;
403
404      for (harg = pos = 0; harg < argc; harg++)
405        if (harg == 0 || harg != hide2) {
406          if (harg == 0 || harg != hide1)
407            pos += snprintf(title + pos, sizeof title - pos, "%s%s",
408                            harg ? " " : "", argv[harg]);
409          else if (hide1off > 1)
410            pos += snprintf(title + pos, sizeof title - pos, " %.*s",
411                            hide1off, argv[harg]);
412        }
413#ifdef __FreeBSD__
414      setproctitle("-%s", title);
415#else
416      setproctitle("%s", title);
417#endif
418    }
419
420    if (*argv[arg] == '/') {
421        sock = (struct sockaddr *)&ifsun;
422        socksz = sizeof ifsun;
423
424        memset(&ifsun, '\0', sizeof ifsun);
425        ifsun.sun_len = strlen(argv[arg]);
426        if (ifsun.sun_len > sizeof ifsun.sun_path - 1) {
427            warnx("%s: path too long", argv[arg]);
428            return 1;
429        }
430        ifsun.sun_family = AF_LOCAL;
431        strcpy(ifsun.sun_path, argv[arg]);
432
433        if (fd = socket(AF_LOCAL, SOCK_STREAM, 0), fd < 0) {
434            warnx("cannot create local domain socket");
435            return 2;
436        }
437    } else {
438        char *port, *host, *colon;
439        int hlen;
440
441        colon = strchr(argv[arg], ':');
442        if (colon) {
443            port = colon + 1;
444            *colon = '\0';
445            host = argv[arg];
446        } else {
447            port = argv[arg];
448            host = "127.0.0.1";
449        }
450        sock = (struct sockaddr *)&ifsin;
451        socksz = sizeof ifsin;
452        hlen = strlen(host);
453
454        memset(&ifsin, '\0', sizeof ifsin);
455        if (strspn(host, "0123456789.") == hlen) {
456            if (!inet_aton(host, &ifsin.sin_addr)) {
457                warnx("cannot translate %s", host);
458                return 1;
459            }
460        } else if ((h = gethostbyname(host)) == 0) {
461            warnx("cannot resolve %s", host);
462            return 1;
463        }
464        else
465            ifsin.sin_addr.s_addr = *(u_long *)h->h_addr_list[0];
466
467        if (colon)
468            *colon = ':';
469
470        if (strspn(port, "0123456789") == strlen(port))
471            ifsin.sin_port = htons(atoi(port));
472        else if (s = getservbyname(port, "tcp"), !s) {
473            warnx("%s isn't a valid port or service!", port);
474            usage();
475        }
476        else
477            ifsin.sin_port = s->s_port;
478
479        ifsin.sin_len = sizeof(ifsin);
480        ifsin.sin_family = AF_INET;
481
482        if (fd = socket(AF_INET, SOCK_STREAM, 0), fd < 0) {
483            warnx("cannot create internet socket");
484            return 2;
485        }
486    }
487
488    TimedOut = 0;
489    if (TimeoutVal) {
490        act.sa_handler = Timeout;
491        sigemptyset(&act.sa_mask);
492        act.sa_flags = 0;
493        sigaction(SIGALRM, &act, &oact);
494        alarm(TimeoutVal);
495    }
496
497    if (connect(fd, sock, socksz) < 0) {
498        if (TimeoutVal) {
499            save_errno = errno;
500            alarm(0);
501            sigaction(SIGALRM, &oact, 0);
502            errno = save_errno;
503        }
504        if (TimedOut)
505            warnx("timeout: cannot connect to socket %s", argv[arg]);
506        else {
507            if (errno)
508                warn("cannot connect to socket %s", argv[arg]);
509            else
510                warnx("cannot connect to socket %s", argv[arg]);
511        }
512        close(fd);
513        return 3;
514    }
515
516    if (TimeoutVal) {
517        alarm(0);
518        sigaction(SIGALRM, &oact, 0);
519    }
520
521    len = 0;
522    Command[sizeof(Command)-1] = '\0';
523    for (arg++; arg < argc; arg++) {
524        if (len && len < sizeof(Command)-1)
525            strcpy(Command+len++, " ");
526        strncpy(Command+len, argv[arg], sizeof(Command)-len-1);
527        len += strlen(Command+len);
528    }
529
530    switch (Receive(fd, verbose | REC_PASSWD)) {
531        case 1:
532            fprintf(stderr, "Password incorrect\n");
533            break;
534
535        case 0:
536            passwd = NULL;
537            if (len == 0) {
538                struct thread_data td;
539                const char *env;
540                int size;
541#ifdef __NetBSD__
542                HistEvent hev = { 0, "" };
543#endif
544
545                td.hist = history_init();
546                if ((env = getenv("EL_SIZE"))) {
547                    size = atoi(env);
548                    if (size < 0)
549                      size = 20;
550                } else
551                    size = 20;
552#ifdef __NetBSD__
553                history(td.hist, &hev, H_SETSIZE, size);
554                td.edit = el_init("pppctl", stdin, stdout, stderr);
555#else
556                history(td.hist, H_EVENT, size);
557                td.edit = el_init("pppctl", stdin, stdout);
558#endif
559                el_source(td.edit, NULL);
560                el_set(td.edit, EL_PROMPT, GetPrompt);
561                if ((env = getenv("EL_EDITOR"))) {
562                    if (!strcmp(env, "vi"))
563                        el_set(td.edit, EL_EDITOR, "vi");
564                    else if (!strcmp(env, "emacs"))
565                        el_set(td.edit, EL_EDITOR, "emacs");
566                }
567                el_set(td.edit, EL_SIGNAL, 1);
568                el_set(td.edit, EL_HIST, history, (const char *)td.hist);
569
570                td.ppp = fd;
571                td.trm = NULL;
572
573                /*
574                 * We create two threads.  The Terminal thread does all the
575                 * work while the Monitor thread simply tells the Terminal
576                 * thread when ``fd'' becomes readable.  The telling is done
577                 * by sending a SIGUSR1 to the Terminal thread.  The
578                 * sem_select semaphore is used to prevent the monitor
579                 * thread from firing excessive signals at the Terminal
580                 * thread (it's abused for exit handling too - see below).
581                 *
582                 * The Terminal thread never uses td.trm !
583                 */
584                sem_init(&sem_select, 0, 0);
585
586                pthread_create(&td.trm, NULL, Terminal, &td);
587                pthread_create(&mon, NULL, Monitor, &td);
588
589                /* Wait for the terminal thread to finish */
590                pthread_join(td.trm, &thread_ret);
591                fprintf(stderr, "Connection closed\n");
592
593                /* Get rid of the monitor thread by abusing sem_select */
594                timetogo = 1;
595                close(fd);
596                fd = -1;
597                sem_post(&sem_select);
598                pthread_join(mon, &thread_ret);
599
600                /* Restore our terminal and release resources */
601                el_end(td.edit);
602                history_end(td.hist);
603                sem_destroy(&sem_select);
604            } else {
605                start = Command;
606                do {
607                    next = strchr(start, ';');
608                    while (*start == ' ' || *start == '\t')
609                        start++;
610                    if (next)
611                        *next = '\0';
612                    strcpy(Buffer, start);
613                    Buffer[sizeof(Buffer)-2] = '\0';
614                    strcat(Buffer, "\n");
615                    if (verbose)
616                        write(1, Buffer, strlen(Buffer));
617                    write(fd, Buffer, strlen(Buffer));
618                    if (Receive(fd, verbose | REC_SHOW) != 0) {
619                        fprintf(stderr, "Connection closed\n");
620                        break;
621                    }
622                    if (next)
623                        start = ++next;
624                } while (next && *next);
625                if (verbose)
626                    puts("");
627            }
628            break;
629
630        default:
631            warnx("ppp is not responding");
632            break;
633    }
634
635    if (fd != -1)
636        close(fd);
637
638    return 0;
639}
640