xref: /freebsd/usr.sbin/pppctl/pppctl.c (revision 17d6c636720d00f77e5d098daf4c278f89d84f7b)
1 /*-
2  * Copyright (c) 1997 Brian Somers <brian@Awfulhak.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  * $FreeBSD$
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     struct timeval t;
122     int Result;
123     char *last;
124     fd_set f;
125     int len;
126 
127     FD_ZERO(&f);
128     FD_SET(fd, &f);
129     t.tv_sec = 0;
130     t.tv_usec = 100000;
131     prompt = Buffer;
132     len = 0;
133 
134     while (Result = read(fd, Buffer+len, sizeof(Buffer)-len-1), Result != -1) {
135         if (Result == 0 && errno != EINTR) {
136             Result = -1;
137             break;
138         }
139         len += Result;
140         Buffer[len] = '\0';
141         if (len > 2 && !strcmp(Buffer+len-2, "> ")) {
142             prompt = strrchr(Buffer, '\n');
143             if (display & (REC_SHOW|REC_VERBOSE)) {
144                 if (display & REC_VERBOSE)
145                     last = Buffer+len-1;
146                 else
147                     last = prompt;
148                 if (last) {
149                     last++;
150                     write(STDOUT_FILENO, Buffer, last-Buffer);
151                 }
152             }
153             prompt = prompt == NULL ? Buffer : prompt+1;
154             for (last = Buffer+len-2; last > Buffer && *last != ' '; last--)
155                 ;
156             if (last > Buffer+3 && !strncmp(last-3, " on", 3)) {
157                  /* a password is required ! */
158                  if (display & REC_PASSWD) {
159                     /* password time */
160                     if (!passwd)
161                         passwd = getpass("Password: ");
162                     sprintf(Buffer, "passwd %s\n", passwd);
163                     memset(passwd, '\0', strlen(passwd));
164                     if (display & REC_VERBOSE)
165                         write(STDOUT_FILENO, Buffer, strlen(Buffer));
166                     write(fd, Buffer, strlen(Buffer));
167                     memset(Buffer, '\0', strlen(Buffer));
168                     return Receive(fd, display & ~REC_PASSWD);
169                 }
170                 Result = 1;
171             } else
172                 Result = 0;
173             break;
174         } else
175             prompt = "";
176         if (len == sizeof Buffer - 1) {
177             int flush;
178             if ((last = strrchr(Buffer, '\n')) == NULL)
179                 /* Yeuch - this is one mother of a line ! */
180                 flush = sizeof Buffer / 2;
181             else
182                 flush = last - Buffer + 1;
183             write(STDOUT_FILENO, Buffer, flush);
184             strcpy(Buffer, Buffer + flush);
185             len -= flush;
186         }
187         if ((Result = select(fd + 1, &f, NULL, NULL, &t)) <= 0) {
188             if (len)
189                 write(STDOUT_FILENO, Buffer, len);
190             break;
191         }
192     }
193 
194     return Result;
195 }
196 
197 /*
198  * Handle being told by the Monitor thread that there's data to be read
199  * on the ppp descriptor.
200  *
201  * Note, this is a signal handler - be careful of what we do !
202  */
203 static void
204 InputHandler(int sig)
205 {
206     static char buf[LINELEN];
207     struct timeval t;
208     int len;
209     fd_set f;
210 
211     if (data != -1) {
212         FD_ZERO(&f);
213         FD_SET(data, &f);
214         t.tv_sec = t.tv_usec = 0;
215 
216         if (select(data + 1, &f, NULL, NULL, &t) > 0) {
217             len = read(data, buf, sizeof buf);
218 
219             if (len > 0)
220                 write(STDOUT_FILENO, buf, len);
221             else if (data != -1)
222                 longjmp(pppdead, -1);
223         }
224 
225         sem_post(&sem_select);
226     } else
227         /* Don't let the Monitor thread in 'till we've set ``data'' up again */
228         want_sem_post = 1;
229 }
230 
231 /*
232  * This is a simple wrapper for el_gets(), allowing our SIGUSR1 signal
233  * handler (above) to take effect only after we've done a setjmp().
234  *
235  * We don't want it to do anything outside of here as we're going to
236  * service the ppp descriptor anyway.
237  */
238 static const char *
239 SmartGets(EditLine *e, int *count, int fd)
240 {
241     const char *result;
242 
243     if (setjmp(pppdead))
244         result = NULL;
245     else {
246         data = fd;
247         if (want_sem_post)
248             /* Let the Monitor thread in again */
249             sem_post(&sem_select);
250         result = el_gets(e, count);
251     }
252 
253     data = -1;
254 
255     return result;
256 }
257 
258 /*
259  * 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  */
269 static void *
270 Terminal(void *v)
271 {
272     struct sigaction act, oact;
273     struct thread_data *td;
274     const char *l;
275     int len;
276 #ifndef __OpenBSD__
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 __OpenBSD__
291             history(td->hist, H_ENTER, l);
292 #else
293             history(td->hist, &hev, 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  */
313 static void *
314 Monitor(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  */
340 int
341 main(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 n, 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             n = snprintf(title + pos, sizeof title - pos, "%s%s",
408                             harg ? " " : "", argv[harg]);
409           else if (hide1off > 1)
410             n = snprintf(title + pos, sizeof title - pos, " %.*s",
411                             hide1off, argv[harg]);
412           else
413             n = 0;
414           if (n < 0 || n >= sizeof title - pos)
415             break;
416           pos += n;
417         }
418 #ifdef __FreeBSD__
419       setproctitle("-%s", title);
420 #else
421       setproctitle("%s", title);
422 #endif
423     }
424 
425     if (*argv[arg] == '/') {
426         sock = (struct sockaddr *)&ifsun;
427         socksz = sizeof ifsun;
428 
429         memset(&ifsun, '\0', sizeof ifsun);
430         ifsun.sun_len = strlen(argv[arg]);
431         if (ifsun.sun_len > sizeof ifsun.sun_path - 1) {
432             warnx("%s: path too long", argv[arg]);
433             return 1;
434         }
435         ifsun.sun_family = AF_LOCAL;
436         strcpy(ifsun.sun_path, argv[arg]);
437 
438         if (fd = socket(AF_LOCAL, SOCK_STREAM, 0), fd < 0) {
439             warnx("cannot create local domain socket");
440             return 2;
441         }
442     } else {
443         char *port, *host, *colon;
444         int hlen;
445 
446         colon = strchr(argv[arg], ':');
447         if (colon) {
448             port = colon + 1;
449             *colon = '\0';
450             host = argv[arg];
451         } else {
452             port = argv[arg];
453             host = "127.0.0.1";
454         }
455         sock = (struct sockaddr *)&ifsin;
456         socksz = sizeof ifsin;
457         hlen = strlen(host);
458 
459         memset(&ifsin, '\0', sizeof ifsin);
460         if (strspn(host, "0123456789.") == hlen) {
461             if (!inet_aton(host, &ifsin.sin_addr)) {
462                 warnx("cannot translate %s", host);
463                 return 1;
464             }
465         } else if ((h = gethostbyname(host)) == 0) {
466             warnx("cannot resolve %s", host);
467             return 1;
468         }
469         else
470             ifsin.sin_addr.s_addr = *(u_long *)h->h_addr_list[0];
471 
472         if (colon)
473             *colon = ':';
474 
475         if (strspn(port, "0123456789") == strlen(port))
476             ifsin.sin_port = htons(atoi(port));
477         else if (s = getservbyname(port, "tcp"), !s) {
478             warnx("%s isn't a valid port or service!", port);
479             usage();
480         }
481         else
482             ifsin.sin_port = s->s_port;
483 
484         ifsin.sin_len = sizeof(ifsin);
485         ifsin.sin_family = AF_INET;
486 
487         if (fd = socket(AF_INET, SOCK_STREAM, 0), fd < 0) {
488             warnx("cannot create internet socket");
489             return 2;
490         }
491     }
492 
493     TimedOut = 0;
494     if (TimeoutVal) {
495         act.sa_handler = Timeout;
496         sigemptyset(&act.sa_mask);
497         act.sa_flags = 0;
498         sigaction(SIGALRM, &act, &oact);
499         alarm(TimeoutVal);
500     }
501 
502     if (connect(fd, sock, socksz) < 0) {
503         if (TimeoutVal) {
504             save_errno = errno;
505             alarm(0);
506             sigaction(SIGALRM, &oact, 0);
507             errno = save_errno;
508         }
509         if (TimedOut)
510             warnx("timeout: cannot connect to socket %s", argv[arg]);
511         else {
512             if (errno)
513                 warn("cannot connect to socket %s", argv[arg]);
514             else
515                 warnx("cannot connect to socket %s", argv[arg]);
516         }
517         close(fd);
518         return 3;
519     }
520 
521     if (TimeoutVal) {
522         alarm(0);
523         sigaction(SIGALRM, &oact, 0);
524     }
525 
526     len = 0;
527     Command[sizeof(Command)-1] = '\0';
528     for (arg++; arg < argc; arg++) {
529         if (len && len < sizeof(Command)-1)
530             strcpy(Command+len++, " ");
531         strncpy(Command+len, argv[arg], sizeof(Command)-len-1);
532         len += strlen(Command+len);
533     }
534 
535     switch (Receive(fd, verbose | REC_PASSWD)) {
536         case 1:
537             fprintf(stderr, "Password incorrect\n");
538             break;
539 
540         case 0:
541             passwd = NULL;
542             if (len == 0) {
543                 struct thread_data td;
544                 const char *env;
545                 int size;
546 #ifndef __OpenBSD__
547                 HistEvent hev = { 0, "" };
548 #endif
549 
550                 td.hist = history_init();
551                 if ((env = getenv("EL_SIZE"))) {
552                     size = atoi(env);
553                     if (size < 0)
554                       size = 20;
555                 } else
556                     size = 20;
557 #ifdef __OpenBSD__
558                 history(td.hist, H_EVENT, size);
559                 td.edit = el_init("pppctl", stdin, stdout);
560 #else
561                 history(td.hist, &hev, H_SETSIZE, size);
562                 td.edit = el_init("pppctl", stdin, stdout, stderr);
563 #endif
564                 el_source(td.edit, NULL);
565                 el_set(td.edit, EL_PROMPT, GetPrompt);
566                 if ((env = getenv("EL_EDITOR"))) {
567                     if (!strcmp(env, "vi"))
568                         el_set(td.edit, EL_EDITOR, "vi");
569                     else if (!strcmp(env, "emacs"))
570                         el_set(td.edit, EL_EDITOR, "emacs");
571                 }
572                 el_set(td.edit, EL_SIGNAL, 1);
573                 el_set(td.edit, EL_HIST, history, (const char *)td.hist);
574 
575                 td.ppp = fd;
576                 td.trm = NULL;
577 
578                 /*
579                  * We create two threads.  The Terminal thread does all the
580                  * work while the Monitor thread simply tells the Terminal
581                  * thread when ``fd'' becomes readable.  The telling is done
582                  * by sending a SIGUSR1 to the Terminal thread.  The
583                  * sem_select semaphore is used to prevent the monitor
584                  * thread from firing excessive signals at the Terminal
585                  * thread (it's abused for exit handling too - see below).
586                  *
587                  * The Terminal thread never uses td.trm !
588                  */
589                 sem_init(&sem_select, 0, 0);
590 
591                 pthread_create(&td.trm, NULL, Terminal, &td);
592                 pthread_create(&mon, NULL, Monitor, &td);
593 
594                 /* Wait for the terminal thread to finish */
595                 pthread_join(td.trm, &thread_ret);
596                 fprintf(stderr, "Connection closed\n");
597 
598                 /* Get rid of the monitor thread by abusing sem_select */
599                 timetogo = 1;
600                 close(fd);
601                 fd = -1;
602                 sem_post(&sem_select);
603                 pthread_join(mon, &thread_ret);
604 
605                 /* Restore our terminal and release resources */
606                 el_end(td.edit);
607                 history_end(td.hist);
608                 sem_destroy(&sem_select);
609             } else {
610                 start = Command;
611                 do {
612                     next = strchr(start, ';');
613                     while (*start == ' ' || *start == '\t')
614                         start++;
615                     if (next)
616                         *next = '\0';
617                     strcpy(Buffer, start);
618                     Buffer[sizeof(Buffer)-2] = '\0';
619                     strcat(Buffer, "\n");
620                     if (verbose)
621                         write(STDOUT_FILENO, Buffer, strlen(Buffer));
622                     write(fd, Buffer, strlen(Buffer));
623                     if (Receive(fd, verbose | REC_SHOW) != 0) {
624                         fprintf(stderr, "Connection closed\n");
625                         break;
626                     }
627                     if (next)
628                         start = ++next;
629                 } while (next && *next);
630                 if (verbose)
631                     write(STDOUT_FILENO, "quit\n", 5);
632                 write(fd, "quit\n", 5);
633                 while (Receive(fd, verbose | REC_SHOW) == 0)
634                     ;
635                 if (verbose)
636                     puts("");
637             }
638             break;
639 
640         default:
641             warnx("ppp is not responding");
642             break;
643     }
644 
645     if (fd != -1)
646         close(fd);
647 
648     return 0;
649 }
650