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