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