xref: /freebsd/usr.sbin/pppctl/pppctl.c (revision 77a0943ded95b9e6438f7db70c4a28e4d93946d4)
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(1, 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(1, 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(1, 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(1, 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(1, 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 
277     act.sa_handler = InputHandler;
278     sigemptyset(&act.sa_mask);
279     act.sa_flags = SA_RESTART;
280     sigaction(SIGUSR1, &act, &oact);
281 
282     td = (struct thread_data *)v;
283     want_sem_post = 1;
284 
285     while ((l = SmartGets(td->edit, &len, td->ppp))) {
286         if (len > 1)
287 #ifdef __NetBSD__
288             history(td->hist, NULL, H_ENTER, l);
289 #else
290             history(td->hist, H_ENTER, l);
291 #endif
292         write(td->ppp, l, len);
293         if (Receive(td->ppp, REC_SHOW) != 0)
294             break;
295     }
296 
297     return NULL;
298 }
299 
300 /*
301  * The Monitor thread entry point.
302  *
303  * This thread simply monitors our ppp descriptor.  When there's something
304  * to read, a SIGUSR1 is sent to the Terminal thread.
305  *
306  * sem_select() is used by the Terminal thread to keep us from sending
307  * flurries of SIGUSR1s, and is used from the main thread to wake us up
308  * when it's time to exit.
309  */
310 static void *
311 Monitor(void *v)
312 {
313     struct thread_data *td;
314     fd_set f;
315     int ret;
316 
317     td = (struct thread_data *)v;
318     FD_ZERO(&f);
319     FD_SET(td->ppp, &f);
320 
321     sem_wait(&sem_select);
322     while (!timetogo)
323         if ((ret = select(td->ppp + 1, &f, NULL, NULL, NULL)) > 0) {
324             pthread_kill(td->trm, SIGUSR1);
325             sem_wait(&sem_select);
326         }
327 
328     return NULL;
329 }
330 
331 /*
332  * Connect to ppp using either a local domain socket or a tcp socket.
333  *
334  * If we're given arguments, process them and quit, otherwise create two
335  * threads to handle interactive mode.
336  */
337 int
338 main(int argc, char **argv)
339 {
340     struct servent *s;
341     struct hostent *h;
342     struct sockaddr *sock;
343     struct sockaddr_in ifsin;
344     struct sockaddr_un ifsun;
345     int socksz, arg, fd, len, verbose, save_errno, hide1, hide1off, hide2;
346     unsigned TimeoutVal;
347     char *DoneWord = "x", *next, *start;
348     struct sigaction act, oact;
349     void *thread_ret;
350     pthread_t mon;
351     char Command[LINELEN];
352     char Buffer[LINELEN];
353 
354     verbose = 0;
355     TimeoutVal = 2;
356     hide1 = hide1off = hide2 = 0;
357 
358     for (arg = 1; arg < argc; arg++)
359         if (*argv[arg] == '-') {
360             for (start = argv[arg] + 1; *start; start++)
361                 switch (*start) {
362                     case 't':
363                         TimeoutVal = (unsigned)atoi
364                             (start[1] ? start + 1 : argv[++arg]);
365                         start = DoneWord;
366                         break;
367 
368                     case 'v':
369                         verbose = REC_VERBOSE;
370                         break;
371 
372                     case 'p':
373                         if (start[1]) {
374                           hide1 = arg;
375                           hide1off = start - argv[arg];
376                           passwd = start + 1;
377                         } else {
378                           hide1 = arg;
379                           hide1off = start - argv[arg];
380                           passwd = argv[++arg];
381                           hide2 = arg;
382                         }
383                         start = DoneWord;
384                         break;
385 
386                     default:
387                         usage();
388                 }
389         }
390         else
391             break;
392 
393 
394     if (argc < arg + 1)
395         usage();
396 
397     if (hide1) {
398       char title[1024];
399       int pos, harg;
400 
401       for (harg = pos = 0; harg < argc; harg++)
402         if (harg == 0 || harg != hide2) {
403           if (harg == 0 || harg != hide1)
404             pos += snprintf(title + pos, sizeof title - pos, "%s%s",
405                             harg ? " " : "", argv[harg]);
406           else if (hide1off > 1)
407             pos += snprintf(title + pos, sizeof title - pos, " %.*s",
408                             hide1off, argv[harg]);
409         }
410 #ifdef __FreeBSD__
411       setproctitle("-%s", title);
412 #else
413       setproctitle("%s", title);
414 #endif
415     }
416 
417     if (*argv[arg] == '/') {
418         sock = (struct sockaddr *)&ifsun;
419         socksz = sizeof ifsun;
420 
421         memset(&ifsun, '\0', sizeof ifsun);
422         ifsun.sun_len = strlen(argv[arg]);
423         if (ifsun.sun_len > sizeof ifsun.sun_path - 1) {
424             warnx("%s: path too long", argv[arg]);
425             return 1;
426         }
427         ifsun.sun_family = AF_LOCAL;
428         strcpy(ifsun.sun_path, argv[arg]);
429 
430         if (fd = socket(AF_LOCAL, SOCK_STREAM, 0), fd < 0) {
431             warnx("cannot create local domain socket");
432             return 2;
433         }
434     } else {
435         char *port, *host, *colon;
436         int hlen;
437 
438         colon = strchr(argv[arg], ':');
439         if (colon) {
440             port = colon + 1;
441             *colon = '\0';
442             host = argv[arg];
443         } else {
444             port = argv[arg];
445             host = "127.0.0.1";
446         }
447         sock = (struct sockaddr *)&ifsin;
448         socksz = sizeof ifsin;
449         hlen = strlen(host);
450 
451         memset(&ifsin, '\0', sizeof ifsin);
452         if (strspn(host, "0123456789.") == hlen) {
453             if (!inet_aton(host, &ifsin.sin_addr)) {
454                 warnx("cannot translate %s", host);
455                 return 1;
456             }
457         } else if ((h = gethostbyname(host)) == 0) {
458             warnx("cannot resolve %s", host);
459             return 1;
460         }
461         else
462             ifsin.sin_addr.s_addr = *(u_long *)h->h_addr_list[0];
463 
464         if (colon)
465             *colon = ':';
466 
467         if (strspn(port, "0123456789") == strlen(port))
468             ifsin.sin_port = htons(atoi(port));
469         else if (s = getservbyname(port, "tcp"), !s) {
470             warnx("%s isn't a valid port or service!", port);
471             usage();
472         }
473         else
474             ifsin.sin_port = s->s_port;
475 
476         ifsin.sin_len = sizeof(ifsin);
477         ifsin.sin_family = AF_INET;
478 
479         if (fd = socket(AF_INET, SOCK_STREAM, 0), fd < 0) {
480             warnx("cannot create internet socket");
481             return 2;
482         }
483     }
484 
485     TimedOut = 0;
486     if (TimeoutVal) {
487         act.sa_handler = Timeout;
488         sigemptyset(&act.sa_mask);
489         act.sa_flags = 0;
490         sigaction(SIGALRM, &act, &oact);
491         alarm(TimeoutVal);
492     }
493 
494     if (connect(fd, sock, socksz) < 0) {
495         if (TimeoutVal) {
496             save_errno = errno;
497             alarm(0);
498             sigaction(SIGALRM, &oact, 0);
499             errno = save_errno;
500         }
501         if (TimedOut)
502             warnx("timeout: cannot connect to socket %s", argv[arg]);
503         else {
504             if (errno)
505                 warn("cannot connect to socket %s", argv[arg]);
506             else
507                 warnx("cannot connect to socket %s", argv[arg]);
508         }
509         close(fd);
510         return 3;
511     }
512 
513     if (TimeoutVal) {
514         alarm(0);
515         sigaction(SIGALRM, &oact, 0);
516     }
517 
518     len = 0;
519     Command[sizeof(Command)-1] = '\0';
520     for (arg++; arg < argc; arg++) {
521         if (len && len < sizeof(Command)-1)
522             strcpy(Command+len++, " ");
523         strncpy(Command+len, argv[arg], sizeof(Command)-len-1);
524         len += strlen(Command+len);
525     }
526 
527     switch (Receive(fd, verbose | REC_PASSWD)) {
528         case 1:
529             fprintf(stderr, "Password incorrect\n");
530             break;
531 
532         case 0:
533             passwd = NULL;
534             if (len == 0) {
535                 struct thread_data td;
536                 const char *env;
537                 int size;
538 
539                 td.hist = history_init();
540                 if ((env = getenv("EL_SIZE"))) {
541                     size = atoi(env);
542                     if (size < 0)
543                       size = 20;
544                 } else
545                     size = 20;
546 #ifdef __NetBSD__
547                 history(td.hist, NULL, H_SETSIZE, size);
548                 td.edit = el_init("pppctl", stdin, stdout, stderr);
549 #else
550                 history(td.hist, H_EVENT, size);
551                 td.edit = el_init("pppctl", stdin, stdout);
552 #endif
553                 el_source(td.edit, NULL);
554                 el_set(td.edit, EL_PROMPT, GetPrompt);
555                 if ((env = getenv("EL_EDITOR"))) {
556                     if (!strcmp(env, "vi"))
557                         el_set(td.edit, EL_EDITOR, "vi");
558                     else if (!strcmp(env, "emacs"))
559                         el_set(td.edit, EL_EDITOR, "emacs");
560                 }
561                 el_set(td.edit, EL_SIGNAL, 1);
562                 el_set(td.edit, EL_HIST, history, (const char *)td.hist);
563 
564                 td.ppp = fd;
565                 td.trm = NULL;
566 
567                 /*
568                  * We create two threads.  The Terminal thread does all the
569                  * work while the Monitor thread simply tells the Terminal
570                  * thread when ``fd'' becomes readable.  The telling is done
571                  * by sending a SIGUSR1 to the Terminal thread.  The
572                  * sem_select semaphore is used to prevent the monitor
573                  * thread from firing excessive signals at the Terminal
574                  * thread (it's abused for exit handling too - see below).
575                  *
576                  * The Terminal thread never uses td.trm !
577                  */
578                 sem_init(&sem_select, 0, 0);
579 
580                 pthread_create(&td.trm, NULL, Terminal, &td);
581                 pthread_create(&mon, NULL, Monitor, &td);
582 
583                 /* Wait for the terminal thread to finish */
584                 pthread_join(td.trm, &thread_ret);
585                 fprintf(stderr, "Connection closed\n");
586 
587                 /* Get rid of the monitor thread by abusing sem_select */
588                 timetogo = 1;
589                 close(fd);
590                 fd = -1;
591                 sem_post(&sem_select);
592                 pthread_join(mon, &thread_ret);
593 
594                 /* Restore our terminal and release resources */
595                 el_end(td.edit);
596                 history_end(td.hist);
597                 sem_destroy(&sem_select);
598             } else {
599                 start = Command;
600                 do {
601                     next = strchr(start, ';');
602                     while (*start == ' ' || *start == '\t')
603                         start++;
604                     if (next)
605                         *next = '\0';
606                     strcpy(Buffer, start);
607                     Buffer[sizeof(Buffer)-2] = '\0';
608                     strcat(Buffer, "\n");
609                     if (verbose)
610                         write(1, Buffer, strlen(Buffer));
611                     write(fd, Buffer, strlen(Buffer));
612                     if (Receive(fd, verbose | REC_SHOW) != 0) {
613                         fprintf(stderr, "Connection closed\n");
614                         break;
615                     }
616                     if (next)
617                         start = ++next;
618                 } while (next && *next);
619                 if (verbose)
620                     puts("");
621             }
622             break;
623 
624         default:
625             warnx("ppp is not responding");
626             break;
627     }
628 
629     if (fd != -1)
630         close(fd);
631 
632     return 0;
633 }
634