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/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