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