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 #include <sys/types.h> 31 32 #include <sys/socket.h> 33 #include <netinet/in.h> 34 #include <arpa/inet.h> 35 #include <sys/un.h> 36 #include <netdb.h> 37 38 #include <sys/time.h> 39 #include <err.h> 40 #include <errno.h> 41 #include <histedit.h> 42 #include <semaphore.h> 43 #include <pthread.h> 44 #include <setjmp.h> 45 #include <signal.h> 46 #include <stdio.h> 47 #include <stdlib.h> 48 #include <string.h> 49 #include <time.h> 50 #include <unistd.h> 51 52 #define LINELEN 2048 53 54 /* Data passed to the threads we create */ 55 struct thread_data { 56 EditLine *edit; /* libedit stuff */ 57 History *hist; /* libedit stuff */ 58 pthread_t trm; /* Terminal thread (for pthread_kill()) */ 59 int ppp; /* ppp descriptor */ 60 }; 61 62 /* Flags passed to Receive() */ 63 #define REC_PASSWD (1) /* Handle a password request from ppp */ 64 #define REC_SHOW (2) /* Show everything except prompts */ 65 #define REC_VERBOSE (4) /* Show everything */ 66 67 static char *passwd; 68 static char *prompt; /* Tell libedit what the current prompt is */ 69 static int data = -1; /* setjmp() has been done when data != -1 */ 70 static jmp_buf pppdead; /* Jump the Terminal thread out of el_gets() */ 71 static int timetogo; /* Tell the Monitor thread to exit */ 72 static sem_t sem_select; /* select() co-ordination between threads */ 73 static int TimedOut; /* Set if our connect() timed out */ 74 static int want_sem_post; /* Need to let the Monitor thread in ? */ 75 76 /* 77 * How to use pppctl... 78 */ 79 static int 80 usage() 81 { 82 fprintf(stderr, "usage: pppctl [-v] [-t n] [-p passwd] " 83 "Port|LocalSock [command[;command]...]\n"); 84 fprintf(stderr, " -v tells pppctl to output all" 85 " conversation\n"); 86 fprintf(stderr, " -t n specifies a timeout of n" 87 " seconds when connecting (default 2)\n"); 88 fprintf(stderr, " -p passwd specifies your password\n"); 89 exit(1); 90 } 91 92 /* 93 * Handle the SIGALRM received due to a connect() timeout. 94 */ 95 static void 96 Timeout(int Sig) 97 { 98 TimedOut = 1; 99 } 100 101 /* 102 * A callback routine for libedit to find out what the current prompt is. 103 * All the work is done in Receive() below. 104 */ 105 static char * 106 GetPrompt(EditLine *e) 107 { 108 if (prompt == NULL) 109 prompt = ""; 110 return prompt; 111 } 112 113 /* 114 * Receive data from the ppp descriptor. 115 * We also handle password prompts here (if asked via the `display' arg) 116 * and buffer what our prompt looks like (via the `prompt' global). 117 */ 118 static int 119 Receive(int fd, int display) 120 { 121 static char Buffer[LINELEN]; 122 char temp[sizeof(Buffer)]; 123 struct timeval t; 124 int Result; 125 char *last; 126 fd_set f; 127 int len; 128 int err; 129 130 FD_ZERO(&f); 131 FD_SET(fd, &f); 132 t.tv_sec = 0; 133 t.tv_usec = 100000; 134 prompt = Buffer; 135 len = 0; 136 137 while (Result = read(fd, Buffer+len, sizeof(Buffer)-len-1), Result != -1) { 138 if (Result == 0) { 139 Result = -1; 140 break; 141 } 142 len += Result; 143 Buffer[len] = '\0'; 144 if (len > 2 && !strcmp(Buffer+len-2, "> ")) { 145 prompt = strrchr(Buffer, '\n'); 146 if (display & (REC_SHOW|REC_VERBOSE)) { 147 if (display & REC_VERBOSE) 148 last = Buffer+len-1; 149 else 150 last = prompt; 151 if (last) { 152 last++; 153 write(STDOUT_FILENO, Buffer, last-Buffer); 154 } 155 } 156 prompt = prompt == NULL ? Buffer : prompt+1; 157 for (last = Buffer+len-2; last > Buffer && *last != ' '; last--) 158 ; 159 if (last > Buffer+3 && !strncmp(last-3, " on", 3)) { 160 /* a password is required ! */ 161 if (display & REC_PASSWD) { 162 /* password time */ 163 if (!passwd) 164 passwd = getpass("Password: "); 165 sprintf(Buffer, "passwd %s\n", passwd); 166 memset(passwd, '\0', strlen(passwd)); 167 if (display & REC_VERBOSE) 168 write(STDOUT_FILENO, Buffer, strlen(Buffer)); 169 write(fd, Buffer, strlen(Buffer)); 170 memset(Buffer, '\0', strlen(Buffer)); 171 return Receive(fd, display & ~REC_PASSWD); 172 } 173 Result = 1; 174 } else 175 Result = 0; 176 break; 177 } else 178 prompt = ""; 179 if (len == sizeof Buffer - 1) { 180 int flush; 181 if ((last = strrchr(Buffer, '\n')) == NULL) 182 /* Yeuch - this is one mother of a line ! */ 183 flush = sizeof Buffer / 2; 184 else 185 flush = last - Buffer + 1; 186 write(STDOUT_FILENO, Buffer, flush); 187 strcpy(temp, Buffer + flush); 188 strcpy(Buffer, temp); 189 len -= flush; 190 } 191 if ((Result = select(fd + 1, &f, NULL, NULL, &t)) <= 0) { 192 err = Result == -1 ? errno : 0; 193 if (len) 194 write(STDOUT_FILENO, Buffer, len); 195 if (err == EINTR) 196 continue; 197 break; 198 } 199 } 200 201 return Result; 202 } 203 204 /* 205 * Handle being told by the Monitor thread that there's data to be read 206 * on the ppp descriptor. 207 * 208 * Note, this is a signal handler - be careful of what we do ! 209 */ 210 static void 211 InputHandler(int sig) 212 { 213 static char buf[LINELEN]; 214 struct timeval t; 215 int len; 216 fd_set f; 217 218 if (data != -1) { 219 FD_ZERO(&f); 220 FD_SET(data, &f); 221 t.tv_sec = t.tv_usec = 0; 222 223 if (select(data + 1, &f, NULL, NULL, &t) > 0) { 224 len = read(data, buf, sizeof buf); 225 226 if (len > 0) 227 write(STDOUT_FILENO, buf, len); 228 else if (data != -1) 229 longjmp(pppdead, -1); 230 } 231 232 sem_post(&sem_select); 233 } else 234 /* Don't let the Monitor thread in 'till we've set ``data'' up again */ 235 want_sem_post = 1; 236 } 237 238 /* 239 * This is a simple wrapper for el_gets(), allowing our SIGUSR1 signal 240 * handler (above) to take effect only after we've done a setjmp(). 241 * 242 * We don't want it to do anything outside of here as we're going to 243 * service the ppp descriptor anyway. 244 */ 245 static const char * 246 SmartGets(EditLine *e, int *count, int fd) 247 { 248 const char *result; 249 250 if (setjmp(pppdead)) 251 result = NULL; 252 else { 253 data = fd; 254 if (want_sem_post) 255 /* Let the Monitor thread in again */ 256 sem_post(&sem_select); 257 result = el_gets(e, count); 258 } 259 260 data = -1; 261 262 return result; 263 } 264 265 /* 266 * The Terminal thread entry point. 267 * 268 * The bulk of the interactive work is done here. We read the terminal, 269 * write the results to our ppp descriptor and read the results back. 270 * 271 * While reading the terminal (using el_gets()), it's possible to take 272 * a SIGUSR1 from the Monitor thread, telling us that the ppp descriptor 273 * has some data. The data is read and displayed by the signal handler 274 * itself. 275 */ 276 static void * 277 Terminal(void *v) 278 { 279 struct sigaction act, oact; 280 struct thread_data *td; 281 const char *l; 282 int len; 283 #ifndef __OpenBSD__ 284 HistEvent hev = { 0, "" }; 285 #endif 286 287 act.sa_handler = InputHandler; 288 sigemptyset(&act.sa_mask); 289 act.sa_flags = SA_RESTART; 290 sigaction(SIGUSR1, &act, &oact); 291 292 td = (struct thread_data *)v; 293 want_sem_post = 1; 294 295 while ((l = SmartGets(td->edit, &len, td->ppp))) { 296 if (len > 1) 297 #ifdef __OpenBSD__ 298 history(td->hist, H_ENTER, l); 299 #else 300 history(td->hist, &hev, H_ENTER, l); 301 #endif 302 write(td->ppp, l, len); 303 if (Receive(td->ppp, REC_SHOW) != 0) 304 break; 305 } 306 307 return NULL; 308 } 309 310 /* 311 * The Monitor thread entry point. 312 * 313 * This thread simply monitors our ppp descriptor. When there's something 314 * to read, a SIGUSR1 is sent to the Terminal thread. 315 * 316 * sem_select() is used by the Terminal thread to keep us from sending 317 * flurries of SIGUSR1s, and is used from the main thread to wake us up 318 * when it's time to exit. 319 */ 320 static void * 321 Monitor(void *v) 322 { 323 struct thread_data *td; 324 fd_set f; 325 int ret; 326 327 td = (struct thread_data *)v; 328 FD_ZERO(&f); 329 FD_SET(td->ppp, &f); 330 331 sem_wait(&sem_select); 332 while (!timetogo) 333 if ((ret = select(td->ppp + 1, &f, NULL, NULL, NULL)) > 0) { 334 pthread_kill(td->trm, SIGUSR1); 335 sem_wait(&sem_select); 336 } 337 338 return NULL; 339 } 340 341 static const char * 342 sockaddr_ntop(const struct sockaddr *sa) 343 { 344 const void *addr; 345 static char addrbuf[INET6_ADDRSTRLEN]; 346 347 switch (sa->sa_family) { 348 case AF_INET: 349 addr = &((const struct sockaddr_in *)sa)->sin_addr; 350 break; 351 case AF_UNIX: 352 addr = &((const struct sockaddr_un *)sa)->sun_path; 353 break; 354 case AF_INET6: 355 addr = &((const struct sockaddr_in6 *)sa)->sin6_addr; 356 break; 357 default: 358 return NULL; 359 } 360 inet_ntop(sa->sa_family, addr, addrbuf, sizeof(addrbuf)); 361 return addrbuf; 362 } 363 364 /* 365 * Connect to ppp using either a local domain socket or a tcp socket. 366 * 367 * If we're given arguments, process them and quit, otherwise create two 368 * threads to handle interactive mode. 369 */ 370 int 371 main(int argc, char **argv) 372 { 373 struct sockaddr_un ifsun; 374 int n, arg, fd, len, verbose, save_errno, hide1, hide1off, hide2; 375 unsigned TimeoutVal; 376 char *DoneWord = "x", *next, *start; 377 struct sigaction act, oact; 378 void *thread_ret; 379 pthread_t mon; 380 char Command[LINELEN]; 381 char Buffer[LINELEN]; 382 383 verbose = 0; 384 TimeoutVal = 2; 385 hide1 = hide1off = hide2 = 0; 386 387 for (arg = 1; arg < argc; arg++) 388 if (*argv[arg] == '-') { 389 for (start = argv[arg] + 1; *start; start++) 390 switch (*start) { 391 case 't': 392 TimeoutVal = (unsigned)atoi 393 (start[1] ? start + 1 : argv[++arg]); 394 start = DoneWord; 395 break; 396 397 case 'v': 398 verbose = REC_VERBOSE; 399 break; 400 401 case 'p': 402 if (start[1]) { 403 hide1 = arg; 404 hide1off = start - argv[arg]; 405 passwd = start + 1; 406 } else { 407 hide1 = arg; 408 hide1off = start - argv[arg]; 409 passwd = argv[++arg]; 410 hide2 = arg; 411 } 412 start = DoneWord; 413 break; 414 415 default: 416 usage(); 417 } 418 } 419 else 420 break; 421 422 423 if (argc < arg + 1) 424 usage(); 425 426 if (hide1) { 427 char title[1024]; 428 int pos, harg; 429 430 for (harg = pos = 0; harg < argc; harg++) 431 if (harg == 0 || harg != hide2) { 432 if (harg == 0 || harg != hide1) 433 n = snprintf(title + pos, sizeof title - pos, "%s%s", 434 harg ? " " : "", argv[harg]); 435 else if (hide1off > 1) 436 n = snprintf(title + pos, sizeof title - pos, " %.*s", 437 hide1off, argv[harg]); 438 else 439 n = 0; 440 if (n < 0 || n >= sizeof title - pos) 441 break; 442 pos += n; 443 } 444 #ifdef __FreeBSD__ 445 setproctitle("-%s", title); 446 #else 447 setproctitle("%s", title); 448 #endif 449 } 450 451 if (*argv[arg] == '/') { 452 memset(&ifsun, '\0', sizeof ifsun); 453 ifsun.sun_len = strlen(argv[arg]); 454 if (ifsun.sun_len > sizeof ifsun.sun_path - 1) { 455 warnx("%s: path too long", argv[arg]); 456 return 1; 457 } 458 ifsun.sun_family = AF_LOCAL; 459 strcpy(ifsun.sun_path, argv[arg]); 460 461 if (fd = socket(AF_LOCAL, SOCK_STREAM, 0), fd < 0) { 462 warnx("cannot create local domain socket"); 463 return 2; 464 } 465 if (connect(fd, (struct sockaddr *)&ifsun, sizeof(ifsun)) < 0) { 466 if (errno) 467 warn("cannot connect to socket %s", argv[arg]); 468 else 469 warnx("cannot connect to socket %s", argv[arg]); 470 close(fd); 471 return 3; 472 } 473 } else { 474 char *addr, *p, *port; 475 const char *caddr; 476 struct addrinfo hints, *res, *pai; 477 int gai; 478 char local[] = "localhost"; 479 480 addr = argv[arg]; 481 if (addr[strspn(addr, "0123456789")] == '\0') { 482 /* port on local machine */ 483 port = addr; 484 addr = local; 485 } else if (*addr == '[') { 486 /* [addr]:port */ 487 if ((p = strchr(addr, ']')) == NULL) { 488 warnx("%s: mismatched '['", addr); 489 return 1; 490 } 491 addr++; 492 *p++ = '\0'; 493 if (*p != ':') { 494 warnx("%s: missing port", addr); 495 return 1; 496 } 497 port = ++p; 498 } else { 499 /* addr:port */ 500 p = addr + strcspn(addr, ":"); 501 if (*p != ':') { 502 warnx("%s: missing port", addr); 503 return 1; 504 } 505 *p++ = '\0'; 506 port = p; 507 } 508 memset(&hints, 0, sizeof(hints)); 509 hints.ai_socktype = SOCK_STREAM; 510 gai = getaddrinfo(addr, port, &hints, &res); 511 if (gai != 0) { 512 warnx("%s: %s", addr, gai_strerror(gai)); 513 return 1; 514 } 515 for (pai = res; pai != NULL; pai = pai->ai_next) { 516 if (fd = socket(pai->ai_family, pai->ai_socktype, 517 pai->ai_protocol), fd < 0) { 518 warnx("cannot create socket"); 519 continue; 520 } 521 TimedOut = 0; 522 if (TimeoutVal) { 523 act.sa_handler = Timeout; 524 sigemptyset(&act.sa_mask); 525 act.sa_flags = 0; 526 sigaction(SIGALRM, &act, &oact); 527 alarm(TimeoutVal); 528 } 529 if (connect(fd, pai->ai_addr, pai->ai_addrlen) == 0) 530 break; 531 if (TimeoutVal) { 532 save_errno = errno; 533 alarm(0); 534 sigaction(SIGALRM, &oact, 0); 535 errno = save_errno; 536 } 537 caddr = sockaddr_ntop(pai->ai_addr); 538 if (caddr == NULL) 539 caddr = argv[arg]; 540 if (TimedOut) 541 warnx("timeout: cannot connect to %s", caddr); 542 else { 543 if (errno) 544 warn("cannot connect to %s", caddr); 545 else 546 warnx("cannot connect to %s", caddr); 547 } 548 close(fd); 549 } 550 freeaddrinfo(res); 551 if (pai == NULL) 552 return 1; 553 if (TimeoutVal) { 554 alarm(0); 555 sigaction(SIGALRM, &oact, 0); 556 } 557 } 558 559 len = 0; 560 Command[sizeof(Command)-1] = '\0'; 561 for (arg++; arg < argc; arg++) { 562 if (len && len < sizeof(Command)-1) 563 strcpy(Command+len++, " "); 564 strncpy(Command+len, argv[arg], sizeof(Command)-len-1); 565 len += strlen(Command+len); 566 } 567 568 switch (Receive(fd, verbose | REC_PASSWD)) { 569 case 1: 570 fprintf(stderr, "Password incorrect\n"); 571 break; 572 573 case 0: 574 passwd = NULL; 575 if (len == 0) { 576 struct thread_data td; 577 const char *env; 578 int size; 579 #ifndef __OpenBSD__ 580 HistEvent hev = { 0, "" }; 581 #endif 582 583 td.hist = history_init(); 584 if ((env = getenv("EL_SIZE"))) { 585 size = atoi(env); 586 if (size < 0) 587 size = 20; 588 } else 589 size = 20; 590 #ifdef __OpenBSD__ 591 history(td.hist, H_EVENT, size); 592 td.edit = el_init("pppctl", stdin, stdout); 593 #else 594 history(td.hist, &hev, H_SETSIZE, size); 595 td.edit = el_init("pppctl", stdin, stdout, stderr); 596 #endif 597 el_source(td.edit, NULL); 598 el_set(td.edit, EL_PROMPT, GetPrompt); 599 if ((env = getenv("EL_EDITOR"))) { 600 if (!strcmp(env, "vi")) 601 el_set(td.edit, EL_EDITOR, "vi"); 602 else if (!strcmp(env, "emacs")) 603 el_set(td.edit, EL_EDITOR, "emacs"); 604 } 605 el_set(td.edit, EL_SIGNAL, 1); 606 el_set(td.edit, EL_HIST, history, (const char *)td.hist); 607 608 td.ppp = fd; 609 td.trm = NULL; 610 611 /* 612 * We create two threads. The Terminal thread does all the 613 * work while the Monitor thread simply tells the Terminal 614 * thread when ``fd'' becomes readable. The telling is done 615 * by sending a SIGUSR1 to the Terminal thread. The 616 * sem_select semaphore is used to prevent the monitor 617 * thread from firing excessive signals at the Terminal 618 * thread (it's abused for exit handling too - see below). 619 * 620 * The Terminal thread never uses td.trm ! 621 */ 622 sem_init(&sem_select, 0, 0); 623 624 pthread_create(&td.trm, NULL, Terminal, &td); 625 pthread_create(&mon, NULL, Monitor, &td); 626 627 /* Wait for the terminal thread to finish */ 628 pthread_join(td.trm, &thread_ret); 629 fprintf(stderr, "Connection closed\n"); 630 631 /* Get rid of the monitor thread by abusing sem_select */ 632 timetogo = 1; 633 close(fd); 634 fd = -1; 635 sem_post(&sem_select); 636 pthread_join(mon, &thread_ret); 637 638 /* Restore our terminal and release resources */ 639 el_end(td.edit); 640 history_end(td.hist); 641 sem_destroy(&sem_select); 642 } else { 643 start = Command; 644 do { 645 next = strchr(start, ';'); 646 while (*start == ' ' || *start == '\t') 647 start++; 648 if (next) 649 *next = '\0'; 650 strcpy(Buffer, start); 651 Buffer[sizeof(Buffer)-2] = '\0'; 652 strcat(Buffer, "\n"); 653 if (verbose) 654 write(STDOUT_FILENO, Buffer, strlen(Buffer)); 655 write(fd, Buffer, strlen(Buffer)); 656 if (Receive(fd, verbose | REC_SHOW) != 0) { 657 fprintf(stderr, "Connection closed\n"); 658 break; 659 } 660 if (next) 661 start = ++next; 662 } while (next && *next); 663 if (verbose) 664 write(STDOUT_FILENO, "quit\n", 5); 665 write(fd, "quit\n", 5); 666 while (Receive(fd, verbose | REC_SHOW) == 0) 667 ; 668 if (verbose) 669 puts(""); 670 } 671 break; 672 673 default: 674 warnx("ppp is not responding"); 675 break; 676 } 677 678 if (fd != -1) 679 close(fd); 680 681 return 0; 682 } 683