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
usage()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
Timeout(int Sig)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 *
GetPrompt(EditLine * e)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
Receive(int fd,int display)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
InputHandler(int sig)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 *
SmartGets(EditLine * e,int * count,int fd)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 *
Terminal(void * v)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 *
Monitor(void * v)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 *
sockaddr_ntop(const struct sockaddr * sa)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
main(int argc,char ** argv)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