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 */ 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 struct timeval t; 123 int Result; 124 char *last; 125 fd_set f; 126 int len; 127 128 FD_ZERO(&f); 129 FD_SET(fd, &f); 130 t.tv_sec = 0; 131 t.tv_usec = 100000; 132 prompt = Buffer; 133 len = 0; 134 135 while (Result = read(fd, Buffer+len, sizeof(Buffer)-len-1), Result != -1) { 136 if (Result == 0 && errno != EINTR) { 137 Result = -1; 138 break; 139 } 140 len += Result; 141 Buffer[len] = '\0'; 142 if (len > 2 && !strcmp(Buffer+len-2, "> ")) { 143 prompt = strrchr(Buffer, '\n'); 144 if (display & (REC_SHOW|REC_VERBOSE)) { 145 if (display & REC_VERBOSE) 146 last = Buffer+len-1; 147 else 148 last = prompt; 149 if (last) { 150 last++; 151 write(STDOUT_FILENO, Buffer, last-Buffer); 152 } 153 } 154 prompt = prompt == NULL ? Buffer : prompt+1; 155 for (last = Buffer+len-2; last > Buffer && *last != ' '; last--) 156 ; 157 if (last > Buffer+3 && !strncmp(last-3, " on", 3)) { 158 /* a password is required ! */ 159 if (display & REC_PASSWD) { 160 /* password time */ 161 if (!passwd) 162 passwd = getpass("Password: "); 163 sprintf(Buffer, "passwd %s\n", passwd); 164 memset(passwd, '\0', strlen(passwd)); 165 if (display & REC_VERBOSE) 166 write(STDOUT_FILENO, Buffer, strlen(Buffer)); 167 write(fd, Buffer, strlen(Buffer)); 168 memset(Buffer, '\0', strlen(Buffer)); 169 return Receive(fd, display & ~REC_PASSWD); 170 } 171 Result = 1; 172 } else 173 Result = 0; 174 break; 175 } else 176 prompt = ""; 177 if (len == sizeof Buffer - 1) { 178 int flush; 179 if ((last = strrchr(Buffer, '\n')) == NULL) 180 /* Yeuch - this is one mother of a line ! */ 181 flush = sizeof Buffer / 2; 182 else 183 flush = last - Buffer + 1; 184 write(STDOUT_FILENO, Buffer, flush); 185 strcpy(Buffer, Buffer + flush); 186 len -= flush; 187 } 188 if ((Result = select(fd + 1, &f, NULL, NULL, &t)) <= 0) { 189 if (len) 190 write(STDOUT_FILENO, Buffer, len); 191 break; 192 } 193 } 194 195 return Result; 196 } 197 198 /* 199 * Handle being told by the Monitor thread that there's data to be read 200 * on the ppp descriptor. 201 * 202 * Note, this is a signal handler - be careful of what we do ! 203 */ 204 static void 205 InputHandler(int sig) 206 { 207 static char buf[LINELEN]; 208 struct timeval t; 209 int len; 210 fd_set f; 211 212 if (data != -1) { 213 FD_ZERO(&f); 214 FD_SET(data, &f); 215 t.tv_sec = t.tv_usec = 0; 216 217 if (select(data + 1, &f, NULL, NULL, &t) > 0) { 218 len = read(data, buf, sizeof buf); 219 220 if (len > 0) 221 write(STDOUT_FILENO, buf, len); 222 else if (data != -1) 223 longjmp(pppdead, -1); 224 } 225 226 sem_post(&sem_select); 227 } else 228 /* Don't let the Monitor thread in 'till we've set ``data'' up again */ 229 want_sem_post = 1; 230 } 231 232 /* 233 * This is a simple wrapper for el_gets(), allowing our SIGUSR1 signal 234 * handler (above) to take effect only after we've done a setjmp(). 235 * 236 * We don't want it to do anything outside of here as we're going to 237 * service the ppp descriptor anyway. 238 */ 239 static const char * 240 SmartGets(EditLine *e, int *count, int fd) 241 { 242 const char *result; 243 244 if (setjmp(pppdead)) 245 result = NULL; 246 else { 247 data = fd; 248 if (want_sem_post) 249 /* Let the Monitor thread in again */ 250 sem_post(&sem_select); 251 result = el_gets(e, count); 252 } 253 254 data = -1; 255 256 return result; 257 } 258 259 /* 260 * The Terminal thread entry point. 261 * 262 * The bulk of the interactive work is done here. We read the terminal, 263 * write the results to our ppp descriptor and read the results back. 264 * 265 * While reading the terminal (using el_gets()), it's possible to take 266 * a SIGUSR1 from the Monitor thread, telling us that the ppp descriptor 267 * has some data. The data is read and displayed by the signal handler 268 * itself. 269 */ 270 static void * 271 Terminal(void *v) 272 { 273 struct sigaction act, oact; 274 struct thread_data *td; 275 const char *l; 276 int len; 277 #ifndef __OpenBSD__ 278 HistEvent hev = { 0, "" }; 279 #endif 280 281 act.sa_handler = InputHandler; 282 sigemptyset(&act.sa_mask); 283 act.sa_flags = SA_RESTART; 284 sigaction(SIGUSR1, &act, &oact); 285 286 td = (struct thread_data *)v; 287 want_sem_post = 1; 288 289 while ((l = SmartGets(td->edit, &len, td->ppp))) { 290 if (len > 1) 291 #ifdef __OpenBSD__ 292 history(td->hist, H_ENTER, l); 293 #else 294 history(td->hist, &hev, H_ENTER, l); 295 #endif 296 write(td->ppp, l, len); 297 if (Receive(td->ppp, REC_SHOW) != 0) 298 break; 299 } 300 301 return NULL; 302 } 303 304 /* 305 * The Monitor thread entry point. 306 * 307 * This thread simply monitors our ppp descriptor. When there's something 308 * to read, a SIGUSR1 is sent to the Terminal thread. 309 * 310 * sem_select() is used by the Terminal thread to keep us from sending 311 * flurries of SIGUSR1s, and is used from the main thread to wake us up 312 * when it's time to exit. 313 */ 314 static void * 315 Monitor(void *v) 316 { 317 struct thread_data *td; 318 fd_set f; 319 int ret; 320 321 td = (struct thread_data *)v; 322 FD_ZERO(&f); 323 FD_SET(td->ppp, &f); 324 325 sem_wait(&sem_select); 326 while (!timetogo) 327 if ((ret = select(td->ppp + 1, &f, NULL, NULL, NULL)) > 0) { 328 pthread_kill(td->trm, SIGUSR1); 329 sem_wait(&sem_select); 330 } 331 332 return NULL; 333 } 334 335 /* 336 * Connect to ppp using either a local domain socket or a tcp socket. 337 * 338 * If we're given arguments, process them and quit, otherwise create two 339 * threads to handle interactive mode. 340 */ 341 int 342 main(int argc, char **argv) 343 { 344 struct servent *s; 345 struct hostent *h; 346 struct sockaddr *sock; 347 struct sockaddr_in ifsin; 348 struct sockaddr_un ifsun; 349 int n, socksz, arg, fd, len, verbose, save_errno, hide1, hide1off, hide2; 350 unsigned TimeoutVal; 351 char *DoneWord = "x", *next, *start; 352 struct sigaction act, oact; 353 void *thread_ret; 354 pthread_t mon; 355 char Command[LINELEN]; 356 char Buffer[LINELEN]; 357 358 verbose = 0; 359 TimeoutVal = 2; 360 hide1 = hide1off = hide2 = 0; 361 362 for (arg = 1; arg < argc; arg++) 363 if (*argv[arg] == '-') { 364 for (start = argv[arg] + 1; *start; start++) 365 switch (*start) { 366 case 't': 367 TimeoutVal = (unsigned)atoi 368 (start[1] ? start + 1 : argv[++arg]); 369 start = DoneWord; 370 break; 371 372 case 'v': 373 verbose = REC_VERBOSE; 374 break; 375 376 case 'p': 377 if (start[1]) { 378 hide1 = arg; 379 hide1off = start - argv[arg]; 380 passwd = start + 1; 381 } else { 382 hide1 = arg; 383 hide1off = start - argv[arg]; 384 passwd = argv[++arg]; 385 hide2 = arg; 386 } 387 start = DoneWord; 388 break; 389 390 default: 391 usage(); 392 } 393 } 394 else 395 break; 396 397 398 if (argc < arg + 1) 399 usage(); 400 401 if (hide1) { 402 char title[1024]; 403 int pos, harg; 404 405 for (harg = pos = 0; harg < argc; harg++) 406 if (harg == 0 || harg != hide2) { 407 if (harg == 0 || harg != hide1) 408 n = snprintf(title + pos, sizeof title - pos, "%s%s", 409 harg ? " " : "", argv[harg]); 410 else if (hide1off > 1) 411 n = snprintf(title + pos, sizeof title - pos, " %.*s", 412 hide1off, argv[harg]); 413 else 414 n = 0; 415 if (n < 0 || n >= sizeof title - pos) 416 break; 417 pos += n; 418 } 419 #ifdef __FreeBSD__ 420 setproctitle("-%s", title); 421 #else 422 setproctitle("%s", title); 423 #endif 424 } 425 426 if (*argv[arg] == '/') { 427 sock = (struct sockaddr *)&ifsun; 428 socksz = sizeof ifsun; 429 430 memset(&ifsun, '\0', sizeof ifsun); 431 ifsun.sun_len = strlen(argv[arg]); 432 if (ifsun.sun_len > sizeof ifsun.sun_path - 1) { 433 warnx("%s: path too long", argv[arg]); 434 return 1; 435 } 436 ifsun.sun_family = AF_LOCAL; 437 strcpy(ifsun.sun_path, argv[arg]); 438 439 if (fd = socket(AF_LOCAL, SOCK_STREAM, 0), fd < 0) { 440 warnx("cannot create local domain socket"); 441 return 2; 442 } 443 } else { 444 char *port, *host, *colon; 445 int hlen; 446 447 colon = strchr(argv[arg], ':'); 448 if (colon) { 449 port = colon + 1; 450 *colon = '\0'; 451 host = argv[arg]; 452 } else { 453 port = argv[arg]; 454 host = "127.0.0.1"; 455 } 456 sock = (struct sockaddr *)&ifsin; 457 socksz = sizeof ifsin; 458 hlen = strlen(host); 459 460 memset(&ifsin, '\0', sizeof ifsin); 461 if (strspn(host, "0123456789.") == hlen) { 462 if (!inet_aton(host, &ifsin.sin_addr)) { 463 warnx("cannot translate %s", host); 464 return 1; 465 } 466 } else if ((h = gethostbyname(host)) == 0) { 467 warnx("cannot resolve %s", host); 468 return 1; 469 } 470 else 471 ifsin.sin_addr.s_addr = *(u_long *)h->h_addr_list[0]; 472 473 if (colon) 474 *colon = ':'; 475 476 if (strspn(port, "0123456789") == strlen(port)) 477 ifsin.sin_port = htons(atoi(port)); 478 else if (s = getservbyname(port, "tcp"), !s) { 479 warnx("%s isn't a valid port or service!", port); 480 usage(); 481 } 482 else 483 ifsin.sin_port = s->s_port; 484 485 ifsin.sin_len = sizeof(ifsin); 486 ifsin.sin_family = AF_INET; 487 488 if (fd = socket(AF_INET, SOCK_STREAM, 0), fd < 0) { 489 warnx("cannot create internet socket"); 490 return 2; 491 } 492 } 493 494 TimedOut = 0; 495 if (TimeoutVal) { 496 act.sa_handler = Timeout; 497 sigemptyset(&act.sa_mask); 498 act.sa_flags = 0; 499 sigaction(SIGALRM, &act, &oact); 500 alarm(TimeoutVal); 501 } 502 503 if (connect(fd, sock, socksz) < 0) { 504 if (TimeoutVal) { 505 save_errno = errno; 506 alarm(0); 507 sigaction(SIGALRM, &oact, 0); 508 errno = save_errno; 509 } 510 if (TimedOut) 511 warnx("timeout: cannot connect to socket %s", argv[arg]); 512 else { 513 if (errno) 514 warn("cannot connect to socket %s", argv[arg]); 515 else 516 warnx("cannot connect to socket %s", argv[arg]); 517 } 518 close(fd); 519 return 3; 520 } 521 522 if (TimeoutVal) { 523 alarm(0); 524 sigaction(SIGALRM, &oact, 0); 525 } 526 527 len = 0; 528 Command[sizeof(Command)-1] = '\0'; 529 for (arg++; arg < argc; arg++) { 530 if (len && len < sizeof(Command)-1) 531 strcpy(Command+len++, " "); 532 strncpy(Command+len, argv[arg], sizeof(Command)-len-1); 533 len += strlen(Command+len); 534 } 535 536 switch (Receive(fd, verbose | REC_PASSWD)) { 537 case 1: 538 fprintf(stderr, "Password incorrect\n"); 539 break; 540 541 case 0: 542 passwd = NULL; 543 if (len == 0) { 544 struct thread_data td; 545 const char *env; 546 int size; 547 #ifndef __OpenBSD__ 548 HistEvent hev = { 0, "" }; 549 #endif 550 551 td.hist = history_init(); 552 if ((env = getenv("EL_SIZE"))) { 553 size = atoi(env); 554 if (size < 0) 555 size = 20; 556 } else 557 size = 20; 558 #ifdef __OpenBSD__ 559 history(td.hist, H_EVENT, size); 560 td.edit = el_init("pppctl", stdin, stdout); 561 #else 562 history(td.hist, &hev, H_SETSIZE, size); 563 td.edit = el_init("pppctl", stdin, stdout, stderr); 564 #endif 565 el_source(td.edit, NULL); 566 el_set(td.edit, EL_PROMPT, GetPrompt); 567 if ((env = getenv("EL_EDITOR"))) { 568 if (!strcmp(env, "vi")) 569 el_set(td.edit, EL_EDITOR, "vi"); 570 else if (!strcmp(env, "emacs")) 571 el_set(td.edit, EL_EDITOR, "emacs"); 572 } 573 el_set(td.edit, EL_SIGNAL, 1); 574 el_set(td.edit, EL_HIST, history, (const char *)td.hist); 575 576 td.ppp = fd; 577 td.trm = NULL; 578 579 /* 580 * We create two threads. The Terminal thread does all the 581 * work while the Monitor thread simply tells the Terminal 582 * thread when ``fd'' becomes readable. The telling is done 583 * by sending a SIGUSR1 to the Terminal thread. The 584 * sem_select semaphore is used to prevent the monitor 585 * thread from firing excessive signals at the Terminal 586 * thread (it's abused for exit handling too - see below). 587 * 588 * The Terminal thread never uses td.trm ! 589 */ 590 sem_init(&sem_select, 0, 0); 591 592 pthread_create(&td.trm, NULL, Terminal, &td); 593 pthread_create(&mon, NULL, Monitor, &td); 594 595 /* Wait for the terminal thread to finish */ 596 pthread_join(td.trm, &thread_ret); 597 fprintf(stderr, "Connection closed\n"); 598 599 /* Get rid of the monitor thread by abusing sem_select */ 600 timetogo = 1; 601 close(fd); 602 fd = -1; 603 sem_post(&sem_select); 604 pthread_join(mon, &thread_ret); 605 606 /* Restore our terminal and release resources */ 607 el_end(td.edit); 608 history_end(td.hist); 609 sem_destroy(&sem_select); 610 } else { 611 start = Command; 612 do { 613 next = strchr(start, ';'); 614 while (*start == ' ' || *start == '\t') 615 start++; 616 if (next) 617 *next = '\0'; 618 strcpy(Buffer, start); 619 Buffer[sizeof(Buffer)-2] = '\0'; 620 strcat(Buffer, "\n"); 621 if (verbose) 622 write(STDOUT_FILENO, Buffer, strlen(Buffer)); 623 write(fd, Buffer, strlen(Buffer)); 624 if (Receive(fd, verbose | REC_SHOW) != 0) { 625 fprintf(stderr, "Connection closed\n"); 626 break; 627 } 628 if (next) 629 start = ++next; 630 } while (next && *next); 631 if (verbose) 632 write(STDOUT_FILENO, "quit\n", 5); 633 write(fd, "quit\n", 5); 634 while (Receive(fd, verbose | REC_SHOW) == 0) 635 ; 636 if (verbose) 637 puts(""); 638 } 639 break; 640 641 default: 642 warnx("ppp is not responding"); 643 break; 644 } 645 646 if (fd != -1) 647 close(fd); 648 649 return 0; 650 } 651