1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 1997 Brian Somers <brian@Awfulhak.org>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29#include <sys/types.h>
30
31#include <sys/socket.h>
32#include <netinet/in.h>
33#include <arpa/inet.h>
34#include <sys/un.h>
35#include <netdb.h>
36
37#include <sys/time.h>
38#include <err.h>
39#include <errno.h>
40#include <histedit.h>
41#include <semaphore.h>
42#include <pthread.h>
43#include <setjmp.h>
44#include <signal.h>
45#include <stdio.h>
46#include <stdlib.h>
47#include <string.h>
48#include <time.h>
49#include <unistd.h>
50
51#define LINELEN 2048
52
53/* Data passed to the threads we create */
54struct thread_data {
55    EditLine *edit;		/* libedit stuff */
56    History *hist;		/* libedit stuff */
57    pthread_t trm;		/* Terminal thread (for pthread_kill()) */
58    int ppp;			/* ppp descriptor */
59};
60
61/* Flags passed to Receive() */
62#define REC_PASSWD  (1)		/* Handle a password request from ppp */
63#define REC_SHOW    (2)		/* Show everything except prompts */
64#define REC_VERBOSE (4)		/* Show everything */
65
66static char *passwd;
67static char *prompt;		/* Tell libedit what the current prompt is */
68static int data = -1;		/* setjmp() has been done when data != -1 */
69static jmp_buf pppdead;		/* Jump the Terminal thread out of el_gets() */
70static int timetogo;		/* Tell the Monitor thread to exit */
71static sem_t sem_select;	/* select() co-ordination between threads */
72static int TimedOut;		/* Set if our connect() timed out */
73static int want_sem_post;	/* Need to let the Monitor thread in ? */
74
75/*
76 * How to use pppctl...
77 */
78static int
79usage()
80{
81    fprintf(stderr, "usage: pppctl [-v] [-t n] [-p passwd] "
82            "Port|LocalSock [command[;command]...]\n");
83    fprintf(stderr, "              -v tells pppctl to output all"
84            " conversation\n");
85    fprintf(stderr, "              -t n specifies a timeout of n"
86            " seconds when connecting (default 2)\n");
87    fprintf(stderr, "              -p passwd specifies your password\n");
88    exit(1);
89}
90
91/*
92 * Handle the SIGALRM received due to a connect() timeout.
93 */
94static void
95Timeout(int Sig)
96{
97    TimedOut = 1;
98}
99
100/*
101 * A callback routine for libedit to find out what the current prompt is.
102 * All the work is done in Receive() below.
103 */
104static char *
105GetPrompt(EditLine *e)
106{
107    if (prompt == NULL)
108        prompt = "";
109    return prompt;
110}
111
112/*
113 * Receive data from the ppp descriptor.
114 * We also handle password prompts here (if asked via the `display' arg)
115 * and buffer what our prompt looks like (via the `prompt' global).
116 */
117static int
118Receive(int fd, int display)
119{
120    static char Buffer[LINELEN];
121    char temp[sizeof(Buffer)];
122    struct timeval t;
123    int Result;
124    char *last;
125    fd_set f;
126    int len;
127    int err;
128
129    FD_ZERO(&f);
130    FD_SET(fd, &f);
131    t.tv_sec = 0;
132    t.tv_usec = 100000;
133    prompt = Buffer;
134    len = 0;
135
136    while (Result = read(fd, Buffer+len, sizeof(Buffer)-len-1), Result != -1) {
137        if (Result == 0) {
138            Result = -1;
139            break;
140        }
141        len += Result;
142        Buffer[len] = '\0';
143        if (len > 2 && !strcmp(Buffer+len-2, "> ")) {
144            prompt = strrchr(Buffer, '\n');
145            if (display & (REC_SHOW|REC_VERBOSE)) {
146                if (display & REC_VERBOSE)
147                    last = Buffer+len-1;
148                else
149                    last = prompt;
150                if (last) {
151                    last++;
152                    write(STDOUT_FILENO, Buffer, last-Buffer);
153                }
154            }
155            prompt = prompt == NULL ? Buffer : prompt+1;
156            for (last = Buffer+len-2; last > Buffer && *last != ' '; last--)
157                ;
158            if (last > Buffer+3 && !strncmp(last-3, " on", 3)) {
159                 /* a password is required ! */
160                 if (display & REC_PASSWD) {
161                    /* password time */
162                    if (!passwd)
163                        passwd = getpass("Password: ");
164                    sprintf(Buffer, "passwd %s\n", passwd);
165                    memset(passwd, '\0', strlen(passwd));
166                    if (display & REC_VERBOSE)
167                        write(STDOUT_FILENO, Buffer, strlen(Buffer));
168                    write(fd, Buffer, strlen(Buffer));
169                    memset(Buffer, '\0', strlen(Buffer));
170                    return Receive(fd, display & ~REC_PASSWD);
171                }
172                Result = 1;
173            } else
174                Result = 0;
175            break;
176        } else
177            prompt = "";
178        if (len == sizeof Buffer - 1) {
179            int flush;
180            if ((last = strrchr(Buffer, '\n')) == NULL)
181                /* Yeuch - this is one mother of a line ! */
182                flush = sizeof Buffer / 2;
183            else
184                flush = last - Buffer + 1;
185            write(STDOUT_FILENO, Buffer, flush);
186	    strcpy(temp, Buffer + flush);
187	    strcpy(Buffer, temp);
188            len -= flush;
189        }
190        if ((Result = select(fd + 1, &f, NULL, NULL, &t)) <= 0) {
191            err = Result == -1 ? errno : 0;
192            if (len)
193                write(STDOUT_FILENO, Buffer, len);
194            if (err == EINTR)
195                continue;
196            break;
197        }
198    }
199
200    return Result;
201}
202
203/*
204 * Handle being told by the Monitor thread that there's data to be read
205 * on the ppp descriptor.
206 *
207 * Note, this is a signal handler - be careful of what we do !
208 */
209static void
210InputHandler(int sig)
211{
212    static char buf[LINELEN];
213    struct timeval t;
214    int len;
215    fd_set f;
216
217    if (data != -1) {
218        FD_ZERO(&f);
219        FD_SET(data, &f);
220        t.tv_sec = t.tv_usec = 0;
221
222        if (select(data + 1, &f, NULL, NULL, &t) > 0) {
223            len = read(data, buf, sizeof buf);
224
225            if (len > 0)
226                write(STDOUT_FILENO, buf, len);
227            else if (data != -1)
228                longjmp(pppdead, -1);
229        }
230
231        sem_post(&sem_select);
232    } else
233        /* Don't let the Monitor thread in 'till we've set ``data'' up again */
234        want_sem_post = 1;
235}
236
237/*
238 * This is a simple wrapper for el_gets(), allowing our SIGUSR1 signal
239 * handler (above) to take effect only after we've done a setjmp().
240 *
241 * We don't want it to do anything outside of here as we're going to
242 * service the ppp descriptor anyway.
243 */
244static const char *
245SmartGets(EditLine *e, int *count, int fd)
246{
247    const char *result;
248
249    if (setjmp(pppdead))
250        result = NULL;
251    else {
252        data = fd;
253        if (want_sem_post)
254            /* Let the Monitor thread in again */
255            sem_post(&sem_select);
256        result = el_gets(e, count);
257    }
258
259    data = -1;
260
261    return result;
262}
263
264/*
265 * The Terminal thread entry point.
266 *
267 * The bulk of the interactive work is done here.  We read the terminal,
268 * write the results to our ppp descriptor and read the results back.
269 *
270 * While reading the terminal (using el_gets()), it's possible to take
271 * a SIGUSR1 from the Monitor thread, telling us that the ppp descriptor
272 * has some data.  The data is read and displayed by the signal handler
273 * itself.
274 */
275static void *
276Terminal(void *v)
277{
278    struct sigaction act, oact;
279    struct thread_data *td;
280    const char *l;
281    int len;
282#ifndef __OpenBSD__
283    HistEvent hev = { 0, "" };
284#endif
285
286    act.sa_handler = InputHandler;
287    sigemptyset(&act.sa_mask);
288    act.sa_flags = SA_RESTART;
289    sigaction(SIGUSR1, &act, &oact);
290
291    td = (struct thread_data *)v;
292    want_sem_post = 1;
293
294    while ((l = SmartGets(td->edit, &len, td->ppp))) {
295        if (len > 1)
296#ifdef __OpenBSD__
297            history(td->hist, H_ENTER, l);
298#else
299            history(td->hist, &hev, H_ENTER, l);
300#endif
301        write(td->ppp, l, len);
302        if (Receive(td->ppp, REC_SHOW) != 0)
303            break;
304    }
305
306    return NULL;
307}
308
309/*
310 * The Monitor thread entry point.
311 *
312 * This thread simply monitors our ppp descriptor.  When there's something
313 * to read, a SIGUSR1 is sent to the Terminal thread.
314 *
315 * sem_select() is used by the Terminal thread to keep us from sending
316 * flurries of SIGUSR1s, and is used from the main thread to wake us up
317 * when it's time to exit.
318 */
319static void *
320Monitor(void *v)
321{
322    struct thread_data *td;
323    fd_set f;
324    int ret;
325
326    td = (struct thread_data *)v;
327    FD_ZERO(&f);
328    FD_SET(td->ppp, &f);
329
330    sem_wait(&sem_select);
331    while (!timetogo)
332        if ((ret = select(td->ppp + 1, &f, NULL, NULL, NULL)) > 0) {
333            pthread_kill(td->trm, SIGUSR1);
334            sem_wait(&sem_select);
335        }
336
337    return NULL;
338}
339
340static const char *
341sockaddr_ntop(const struct sockaddr *sa)
342{
343    const void *addr;
344    static char addrbuf[INET6_ADDRSTRLEN];
345
346    switch (sa->sa_family) {
347    case AF_INET:
348	addr = &((const struct sockaddr_in *)sa)->sin_addr;
349	break;
350    case AF_UNIX:
351	addr = &((const struct sockaddr_un *)sa)->sun_path;
352	break;
353    case AF_INET6:
354	addr = &((const struct sockaddr_in6 *)sa)->sin6_addr;
355	break;
356    default:
357	return NULL;
358    }
359    inet_ntop(sa->sa_family, addr, addrbuf, sizeof(addrbuf));
360    return addrbuf;
361}
362
363/*
364 * Connect to ppp using either a local domain socket or a tcp socket.
365 *
366 * If we're given arguments, process them and quit, otherwise create two
367 * threads to handle interactive mode.
368 */
369int
370main(int argc, char **argv)
371{
372    struct sockaddr_un ifsun;
373    int n, arg, fd, len, verbose, save_errno, hide1, hide1off, hide2;
374    unsigned TimeoutVal;
375    char *DoneWord = "x", *next, *start;
376    struct sigaction act, oact;
377    void *thread_ret;
378    pthread_t mon;
379    char Command[LINELEN];
380    char Buffer[LINELEN];
381
382    verbose = 0;
383    TimeoutVal = 2;
384    hide1 = hide1off = hide2 = 0;
385
386    for (arg = 1; arg < argc; arg++)
387        if (*argv[arg] == '-') {
388            for (start = argv[arg] + 1; *start; start++)
389                switch (*start) {
390                    case 't':
391                        TimeoutVal = (unsigned)atoi
392                            (start[1] ? start + 1 : argv[++arg]);
393                        start = DoneWord;
394                        break;
395
396                    case 'v':
397                        verbose = REC_VERBOSE;
398                        break;
399
400                    case 'p':
401                        if (start[1]) {
402                          hide1 = arg;
403                          hide1off = start - argv[arg];
404                          passwd = start + 1;
405                        } else {
406                          hide1 = arg;
407                          hide1off = start - argv[arg];
408                          passwd = argv[++arg];
409                          hide2 = arg;
410                        }
411                        start = DoneWord;
412                        break;
413
414                    default:
415                        usage();
416                }
417        }
418        else
419            break;
420
421
422    if (argc < arg + 1)
423        usage();
424
425    if (hide1) {
426      char title[1024];
427      int pos, harg;
428
429      for (harg = pos = 0; harg < argc; harg++)
430        if (harg == 0 || harg != hide2) {
431          if (harg == 0 || harg != hide1)
432            n = snprintf(title + pos, sizeof title - pos, "%s%s",
433                            harg ? " " : "", argv[harg]);
434          else if (hide1off > 1)
435            n = snprintf(title + pos, sizeof title - pos, " %.*s",
436                            hide1off, argv[harg]);
437          else
438            n = 0;
439          if (n < 0 || n >= sizeof title - pos)
440            break;
441          pos += n;
442        }
443#ifdef __FreeBSD__
444      setproctitle("-%s", title);
445#else
446      setproctitle("%s", title);
447#endif
448    }
449
450    if (*argv[arg] == '/') {
451        memset(&ifsun, '\0', sizeof ifsun);
452        ifsun.sun_len = strlen(argv[arg]);
453        if (ifsun.sun_len > sizeof ifsun.sun_path - 1) {
454            warnx("%s: path too long", argv[arg]);
455            return 1;
456        }
457        ifsun.sun_family = AF_LOCAL;
458        strcpy(ifsun.sun_path, argv[arg]);
459
460        if (fd = socket(AF_LOCAL, SOCK_STREAM, 0), fd < 0) {
461            warnx("cannot create local domain socket");
462            return 2;
463        }
464	if (connect(fd, (struct sockaddr *)&ifsun, sizeof(ifsun)) < 0) {
465	    if (errno)
466		warn("cannot connect to socket %s", argv[arg]);
467	    else
468		warnx("cannot connect to socket %s", argv[arg]);
469	    close(fd);
470	    return 3;
471	}
472    } else {
473        char *addr, *p, *port;
474	const char *caddr;
475	struct addrinfo hints, *res, *pai;
476        int gai;
477	char local[] = "localhost";
478
479	addr = argv[arg];
480	if (addr[strspn(addr, "0123456789")] == '\0') {
481	    /* port on local machine */
482	    port = addr;
483	    addr = local;
484	} else if (*addr == '[') {
485	    /* [addr]:port */
486	    if ((p = strchr(addr, ']')) == NULL) {
487		warnx("%s: mismatched '['", addr);
488		return 1;
489	    }
490	    addr++;
491	    *p++ = '\0';
492	    if (*p != ':') {
493		warnx("%s: missing port", addr);
494		return 1;
495	    }
496	    port = ++p;
497	} else {
498	    /* addr:port */
499	    p = addr + strcspn(addr, ":");
500	    if (*p != ':') {
501		warnx("%s: missing port", addr);
502		return 1;
503	    }
504	    *p++ = '\0';
505	    port = p;
506	}
507	memset(&hints, 0, sizeof(hints));
508	hints.ai_socktype = SOCK_STREAM;
509	gai = getaddrinfo(addr, port, &hints, &res);
510	if (gai != 0) {
511	    warnx("%s: %s", addr, gai_strerror(gai));
512	    return 1;
513	}
514	for (pai = res; pai != NULL; pai = pai->ai_next) {
515	    if (fd = socket(pai->ai_family, pai->ai_socktype,
516		pai->ai_protocol), fd < 0) {
517		warnx("cannot create socket");
518		continue;
519	    }
520	    TimedOut = 0;
521	    if (TimeoutVal) {
522		act.sa_handler = Timeout;
523		sigemptyset(&act.sa_mask);
524		act.sa_flags = 0;
525		sigaction(SIGALRM, &act, &oact);
526		alarm(TimeoutVal);
527	    }
528	    if (connect(fd, pai->ai_addr, pai->ai_addrlen) == 0)
529		break;
530	    if (TimeoutVal) {
531		save_errno = errno;
532		alarm(0);
533		sigaction(SIGALRM, &oact, 0);
534		errno = save_errno;
535	    }
536	    caddr = sockaddr_ntop(pai->ai_addr);
537	    if (caddr == NULL)
538		caddr = argv[arg];
539	    if (TimedOut)
540		warnx("timeout: cannot connect to %s", caddr);
541	    else {
542		if (errno)
543		    warn("cannot connect to %s", caddr);
544		else
545		    warnx("cannot connect to %s", caddr);
546	    }
547	    close(fd);
548	}
549	freeaddrinfo(res);
550	if (pai == NULL)
551	    return 1;
552	if (TimeoutVal) {
553	    alarm(0);
554	    sigaction(SIGALRM, &oact, 0);
555	}
556    }
557
558    len = 0;
559    Command[sizeof(Command)-1] = '\0';
560    for (arg++; arg < argc; arg++) {
561        if (len && len < sizeof(Command)-1)
562            strcpy(Command+len++, " ");
563        strncpy(Command+len, argv[arg], sizeof(Command)-len-1);
564        len += strlen(Command+len);
565    }
566
567    switch (Receive(fd, verbose | REC_PASSWD)) {
568        case 1:
569            fprintf(stderr, "Password incorrect\n");
570            break;
571
572        case 0:
573            passwd = NULL;
574            if (len == 0) {
575                struct thread_data td;
576                const char *env;
577                int size;
578#ifndef __OpenBSD__
579                HistEvent hev = { 0, "" };
580#endif
581
582                td.hist = history_init();
583                if ((env = getenv("EL_SIZE"))) {
584                    size = atoi(env);
585                    if (size < 0)
586                      size = 20;
587                } else
588                    size = 20;
589#ifdef __OpenBSD__
590                history(td.hist, H_EVENT, size);
591                td.edit = el_init("pppctl", stdin, stdout);
592#else
593                history(td.hist, &hev, H_SETSIZE, size);
594                td.edit = el_init("pppctl", stdin, stdout, stderr);
595#endif
596                el_source(td.edit, NULL);
597                el_set(td.edit, EL_PROMPT, GetPrompt);
598                if ((env = getenv("EL_EDITOR"))) {
599                    if (!strcmp(env, "vi"))
600                        el_set(td.edit, EL_EDITOR, "vi");
601                    else if (!strcmp(env, "emacs"))
602                        el_set(td.edit, EL_EDITOR, "emacs");
603                }
604                el_set(td.edit, EL_SIGNAL, 1);
605                el_set(td.edit, EL_HIST, history, (const char *)td.hist);
606
607                td.ppp = fd;
608                td.trm = NULL;
609
610                /*
611                 * We create two threads.  The Terminal thread does all the
612                 * work while the Monitor thread simply tells the Terminal
613                 * thread when ``fd'' becomes readable.  The telling is done
614                 * by sending a SIGUSR1 to the Terminal thread.  The
615                 * sem_select semaphore is used to prevent the monitor
616                 * thread from firing excessive signals at the Terminal
617                 * thread (it's abused for exit handling too - see below).
618                 *
619                 * The Terminal thread never uses td.trm !
620                 */
621                sem_init(&sem_select, 0, 0);
622
623                pthread_create(&td.trm, NULL, Terminal, &td);
624                pthread_create(&mon, NULL, Monitor, &td);
625
626                /* Wait for the terminal thread to finish */
627                pthread_join(td.trm, &thread_ret);
628                fprintf(stderr, "Connection closed\n");
629
630                /* Get rid of the monitor thread by abusing sem_select */
631                timetogo = 1;
632                close(fd);
633                fd = -1;
634                sem_post(&sem_select);
635                pthread_join(mon, &thread_ret);
636
637                /* Restore our terminal and release resources */
638                el_end(td.edit);
639                history_end(td.hist);
640                sem_destroy(&sem_select);
641            } else {
642                start = Command;
643                do {
644                    next = strchr(start, ';');
645                    while (*start == ' ' || *start == '\t')
646                        start++;
647                    if (next)
648                        *next = '\0';
649                    strcpy(Buffer, start);
650                    Buffer[sizeof(Buffer)-2] = '\0';
651                    strcat(Buffer, "\n");
652                    if (verbose)
653                        write(STDOUT_FILENO, Buffer, strlen(Buffer));
654                    write(fd, Buffer, strlen(Buffer));
655                    if (Receive(fd, verbose | REC_SHOW) != 0) {
656                        fprintf(stderr, "Connection closed\n");
657                        break;
658                    }
659                    if (next)
660                        start = ++next;
661                } while (next && *next);
662                if (verbose)
663                    write(STDOUT_FILENO, "quit\n", 5);
664                write(fd, "quit\n", 5);
665                while (Receive(fd, verbose | REC_SHOW) == 0)
666                    ;
667                if (verbose)
668                    puts("");
669            }
670            break;
671
672        default:
673            warnx("ppp is not responding");
674            break;
675    }
676
677    if (fd != -1)
678        close(fd);
679
680    return 0;
681}
682