xref: /freebsd/usr.sbin/pppctl/pppctl.c (revision 06c3fb2749bda94cb5201f81ffdb8fa6c3161b2e)
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 */
54 struct 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 
66 static char *passwd;
67 static char *prompt;		/* Tell libedit what the current prompt is */
68 static int data = -1;		/* setjmp() has been done when data != -1 */
69 static jmp_buf pppdead;		/* Jump the Terminal thread out of el_gets() */
70 static int timetogo;		/* Tell the Monitor thread to exit */
71 static sem_t sem_select;	/* select() co-ordination between threads */
72 static int TimedOut;		/* Set if our connect() timed out */
73 static int want_sem_post;	/* Need to let the Monitor thread in ? */
74 
75 /*
76  * How to use pppctl...
77  */
78 static int
79 usage()
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  */
94 static void
95 Timeout(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  */
104 static char *
105 GetPrompt(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  */
117 static int
118 Receive(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  */
209 static void
210 InputHandler(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  */
244 static const char *
245 SmartGets(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  */
275 static void *
276 Terminal(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  */
319 static void *
320 Monitor(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 
340 static const char *
341 sockaddr_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  */
369 int
370 main(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