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