pppctl.c revision 73558
11553Srgrimes/*- 21553Srgrimes * Copyright (c) 1997 Brian Somers <brian@Awfulhak.org> 31553Srgrimes * All rights reserved. 41553Srgrimes * 51553Srgrimes * Redistribution and use in source and binary forms, with or without 61553Srgrimes * modification, are permitted provided that the following conditions 71553Srgrimes * are met: 81553Srgrimes * 1. Redistributions of source code must retain the above copyright 91553Srgrimes * notice, this list of conditions and the following disclaimer. 101553Srgrimes * 2. Redistributions in binary form must reproduce the above copyright 111553Srgrimes * notice, this list of conditions and the following disclaimer in the 121553Srgrimes * documentation and/or other materials provided with the distribution. 13121300Sphk * 141553Srgrimes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 151553Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 161553Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 171553Srgrimes * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 181553Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 191553Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 201553Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 211553Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 221553Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 231553Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 241553Srgrimes * SUCH DAMAGE. 251553Srgrimes * 261553Srgrimes * $FreeBSD: head/usr.sbin/pppctl/pppctl.c 73558 2001-03-05 00:59:53Z brian $ 271553Srgrimes */ 281553Srgrimes 291553Srgrimes#include <sys/types.h> 30114601Sobrien 311553Srgrimes#include <sys/socket.h> 321553Srgrimes#include <netinet/in.h> 33114601Sobrien#include <arpa/inet.h> 3430027Scharnier#include <sys/un.h> 35114601Sobrien#include <netdb.h> 36114601Sobrien 371553Srgrimes#include <sys/time.h> 381553Srgrimes#include <err.h> 391553Srgrimes#include <errno.h> 401553Srgrimes#include <histedit.h> 4130027Scharnier#include <semaphore.h> 4230027Scharnier#include <pthread.h> 431553Srgrimes#include <setjmp.h> 441553Srgrimes#include <signal.h> 4530027Scharnier#include <stdio.h> 461553Srgrimes#include <stdlib.h> 471553Srgrimes#include <string.h> 481553Srgrimes#include <time.h> 491553Srgrimes#include <unistd.h> 501553Srgrimes 511553Srgrimes#define LINELEN 2048 521553Srgrimes 5399800Salfred/* Data passed to the threads we create */ 5499800Salfredstruct thread_data { 551553Srgrimes EditLine *edit; /* libedit stuff */ 561553Srgrimes History *hist; /* libedit stuff */ 57122135Sphk pthread_t trm; /* Terminal thread (for pthread_kill()) */ 581553Srgrimes int ppp; /* ppp descriptor */ 591553Srgrimes}; 601553Srgrimes 61122135Sphk/* Flags passed to Receive() */ 621553Srgrimes#define REC_PASSWD (1) /* Handle a password request from ppp */ 631553Srgrimes#define REC_SHOW (2) /* Show everything except prompts */ 641553Srgrimes#define REC_VERBOSE (4) /* Show everything */ 651553Srgrimes 661553Srgrimesstatic char *passwd; 671553Srgrimesstatic char *prompt; /* Tell libedit what the current prompt is */ 68174403Sdesstatic int data = -1; /* setjmp() has been done when data != -1 */ 69174403Sdesstatic jmp_buf pppdead; /* Jump the Terminal thread out of el_gets() */ 70174403Sdesstatic int timetogo; /* Tell the Monitor thread to exit */ 71174403Sdesstatic sem_t sem_select; /* select() co-ordination between threads */ 72174403Sdesstatic int TimedOut; /* Set if our connect() timed out */ 73174403Sdesstatic int want_sem_post; /* Need to let the Monitor thread in ? */ 74121299Sphk 751553Srgrimes/* 76121299Sphk * How to use pppctl... 77121299Sphk */ 78121299Sphkstatic int 799675Sbdeusage() 801553Srgrimes{ 8199802Salfred fprintf(stderr, "usage: pppctl [-v] [-t n] [-p passwd] " 821553Srgrimes "Port|LocalSock [command[;command]...]\n"); 8399802Salfred fprintf(stderr, " -v tells pppctl to output all" 841553Srgrimes " conversation\n"); 85174403Sdes fprintf(stderr, " -t n specifies a timeout of n" 8630027Scharnier " seconds when connecting (default 2)\n"); 871553Srgrimes fprintf(stderr, " -p passwd specifies your password\n"); 889675Sbde exit(1); 892860Srgrimes} 9060418Swollman 9160418Swollman/* 9260418Swollman * Handle the SIGALRM received due to a connect() timeout. 9360418Swollman */ 941553Srgrimesstatic void 951553SrgrimesTimeout(int Sig) 9663086Sjoe{ 971553Srgrimes TimedOut = 1; 981553Srgrimes} 999675Sbde 1001553Srgrimes/* 1018857Srgrimes * A callback routine for libedit to find out what the current prompt is. 1021553Srgrimes * All the work is done in Receive() below. 1031553Srgrimes */ 1041553Srgrimesstatic char * 1051553SrgrimesGetPrompt(EditLine *e) 1061553Srgrimes{ 1071553Srgrimes if (prompt == NULL) 10830027Scharnier prompt = ""; 1091553Srgrimes return prompt; 1101553Srgrimes} 1111553Srgrimes 1121553Srgrimes/* 1131553Srgrimes * Receive data from the ppp descriptor. 1141553Srgrimes * We also handle password prompts here (if asked via the `display' arg) 1159675Sbde * and buffer what our prompt looks like (via the `prompt' global). 1169675Sbde */ 1171553Srgrimesstatic int 1182860SrgrimesReceive(int fd, int display) 1192860Srgrimes{ 1201553Srgrimes static char Buffer[LINELEN]; 1211553Srgrimes struct timeval t; 12236670Speter int Result; 12336670Speter char *last; 1241553Srgrimes fd_set f; 12536841Speter int len; 1261553Srgrimes 1271553Srgrimes FD_ZERO(&f); 1281553Srgrimes FD_SET(fd, &f); 1291553Srgrimes t.tv_sec = 0; 1301553Srgrimes t.tv_usec = 100000; 1311553Srgrimes prompt = Buffer; 1321553Srgrimes len = 0; 1331553Srgrimes 1341553Srgrimes while (Result = read(fd, Buffer+len, sizeof(Buffer)-len-1), Result != -1) { 1351553Srgrimes if (Result == 0 && errno != EINTR) { 1361553Srgrimes Result = -1; 1379675Sbde break; 1381553Srgrimes } 13966584Sphk len += Result; 1401553Srgrimes Buffer[len] = '\0'; 1419675Sbde if (len > 2 && !strcmp(Buffer+len-2, "> ")) { 1429675Sbde prompt = strrchr(Buffer, '\n'); 1431553Srgrimes if (display & (REC_SHOW|REC_VERBOSE)) { 1441553Srgrimes if (display & REC_VERBOSE) 1451553Srgrimes last = Buffer+len-1; 1461553Srgrimes else 1471553Srgrimes last = prompt; 1481553Srgrimes if (last) { 1491553Srgrimes last++; 1501553Srgrimes write(1, Buffer, last-Buffer); 1511553Srgrimes } 1521553Srgrimes } 1531553Srgrimes prompt = prompt == NULL ? Buffer : prompt+1; 154112214Srobert for (last = Buffer+len-2; last > Buffer && *last != ' '; last--) 1551553Srgrimes ; 1561553Srgrimes if (last > Buffer+3 && !strncmp(last-3, " on", 3)) { 1571553Srgrimes /* a password is required ! */ 1581553Srgrimes if (display & REC_PASSWD) { 159121299Sphk /* password time */ 1601553Srgrimes if (!passwd) 161121299Sphk passwd = getpass("Password: "); 162121299Sphk sprintf(Buffer, "passwd %s\n", passwd); 163148966Sbrian memset(passwd, '\0', strlen(passwd)); 164148966Sbrian if (display & REC_VERBOSE) 1651553Srgrimes write(1, Buffer, strlen(Buffer)); 1661553Srgrimes write(fd, Buffer, strlen(Buffer)); 167160083Smaxim memset(Buffer, '\0', strlen(Buffer)); 168160083Smaxim return Receive(fd, display & ~REC_PASSWD); 1691553Srgrimes } 1701553Srgrimes Result = 1; 1711553Srgrimes } else 17263853Simp Result = 0; 17363853Simp break; 17463853Simp } else 17563853Simp prompt = ""; 176121300Sphk if (len == sizeof Buffer - 1) { 17763853Simp int flush; 17863853Simp if ((last = strrchr(Buffer, '\n')) == NULL) 17963853Simp /* Yeuch - this is one mother of a line ! */ 18066584Sphk flush = sizeof Buffer / 2; 18163853Simp else 18263087Sjoe flush = last - Buffer + 1; 1831553Srgrimes write(1, Buffer, flush); 1841553Srgrimes strcpy(Buffer, Buffer + flush); 1851553Srgrimes len -= flush; 1861553Srgrimes } 1871553Srgrimes if ((Result = select(fd + 1, &f, NULL, NULL, &t)) <= 0) { 18863087Sjoe if (len) 18963087Sjoe write(1, Buffer, len); 19063087Sjoe break; 19163087Sjoe } 19251705Sbillf } 1931553Srgrimes 19463087Sjoe return Result; 1951553Srgrimes} 19663087Sjoe 19763087Sjoe/* 19863087Sjoe * Handle being told by the Monitor thread that there's data to be read 19963087Sjoe * on the ppp descriptor. 20063087Sjoe * 20163087Sjoe * Note, this is a signal handler - be careful of what we do ! 20263087Sjoe */ 203148966Sbrianstatic void 204148966SbrianInputHandler(int sig) 205148966Sbrian{ 206148966Sbrian static char buf[LINELEN]; 207148966Sbrian struct timeval t; 208148966Sbrian int len; 209148966Sbrian fd_set f; 210148966Sbrian 211148966Sbrian if (data != -1) { 212148966Sbrian FD_ZERO(&f); 213148966Sbrian FD_SET(data, &f); 214148966Sbrian t.tv_sec = t.tv_usec = 0; 215148966Sbrian 216148966Sbrian if (select(data + 1, &f, NULL, NULL, &t) > 0) { 21763087Sjoe len = read(data, buf, sizeof buf); 21863087Sjoe 21936670Speter if (len > 0) 2201553Srgrimes write(1, buf, len); 22136670Speter else if (data != -1) 2221553Srgrimes longjmp(pppdead, -1); 2231553Srgrimes } 2241553Srgrimes 2251553Srgrimes sem_post(&sem_select); 2261553Srgrimes } else 22751705Sbillf /* Don't let the Monitor thread in 'till we've set ``data'' up again */ 2281553Srgrimes want_sem_post = 1; 2291553Srgrimes} 2301553Srgrimes 2311553Srgrimes/* 2321553Srgrimes * This is a simple wrapper for el_gets(), allowing our SIGUSR1 signal 2331553Srgrimes * handler (above) to take effect only after we've done a setjmp(). 2341553Srgrimes * 2351553Srgrimes * We don't want it to do anything outside of here as we're going to 2361553Srgrimes * service the ppp descriptor anyway. 2371553Srgrimes */ 238148966Sbrianstatic const char * 239148966SbrianSmartGets(EditLine *e, int *count, int fd) 240148966Sbrian{ 241148966Sbrian const char *result; 242148966Sbrian 243148966Sbrian if (setjmp(pppdead)) 244148966Sbrian result = NULL; 245148966Sbrian else { 246148966Sbrian data = fd; 247148966Sbrian if (want_sem_post) 248148966Sbrian /* Let the Monitor thread in again */ 249148966Sbrian sem_post(&sem_select); 2501553Srgrimes result = el_gets(e, count); 2511553Srgrimes } 2521553Srgrimes 2531553Srgrimes data = -1; 25454375Sjoe 25554375Sjoe return result; 25654375Sjoe} 25754375Sjoe 2581553Srgrimes/* 2591553Srgrimes * 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 */ 269static void * 270Terminal(void *v) 271{ 272 struct sigaction act, oact; 273 struct thread_data *td; 274 const char *l; 275 int len; 276#ifdef __NetBSD__ 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 __NetBSD__ 291 history(td->hist, &hev, H_ENTER, l); 292#else 293 history(td->hist, 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 */ 313static void * 314Monitor(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 */ 340int 341main(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 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 pos += snprintf(title + pos, sizeof title - pos, "%s%s", 408 harg ? " " : "", argv[harg]); 409 else if (hide1off > 1) 410 pos += snprintf(title + pos, sizeof title - pos, " %.*s", 411 hide1off, argv[harg]); 412 } 413#ifdef __FreeBSD__ 414 setproctitle("-%s", title); 415#else 416 setproctitle("%s", title); 417#endif 418 } 419 420 if (*argv[arg] == '/') { 421 sock = (struct sockaddr *)&ifsun; 422 socksz = sizeof ifsun; 423 424 memset(&ifsun, '\0', sizeof ifsun); 425 ifsun.sun_len = strlen(argv[arg]); 426 if (ifsun.sun_len > sizeof ifsun.sun_path - 1) { 427 warnx("%s: path too long", argv[arg]); 428 return 1; 429 } 430 ifsun.sun_family = AF_LOCAL; 431 strcpy(ifsun.sun_path, argv[arg]); 432 433 if (fd = socket(AF_LOCAL, SOCK_STREAM, 0), fd < 0) { 434 warnx("cannot create local domain socket"); 435 return 2; 436 } 437 } else { 438 char *port, *host, *colon; 439 int hlen; 440 441 colon = strchr(argv[arg], ':'); 442 if (colon) { 443 port = colon + 1; 444 *colon = '\0'; 445 host = argv[arg]; 446 } else { 447 port = argv[arg]; 448 host = "127.0.0.1"; 449 } 450 sock = (struct sockaddr *)&ifsin; 451 socksz = sizeof ifsin; 452 hlen = strlen(host); 453 454 memset(&ifsin, '\0', sizeof ifsin); 455 if (strspn(host, "0123456789.") == hlen) { 456 if (!inet_aton(host, &ifsin.sin_addr)) { 457 warnx("cannot translate %s", host); 458 return 1; 459 } 460 } else if ((h = gethostbyname(host)) == 0) { 461 warnx("cannot resolve %s", host); 462 return 1; 463 } 464 else 465 ifsin.sin_addr.s_addr = *(u_long *)h->h_addr_list[0]; 466 467 if (colon) 468 *colon = ':'; 469 470 if (strspn(port, "0123456789") == strlen(port)) 471 ifsin.sin_port = htons(atoi(port)); 472 else if (s = getservbyname(port, "tcp"), !s) { 473 warnx("%s isn't a valid port or service!", port); 474 usage(); 475 } 476 else 477 ifsin.sin_port = s->s_port; 478 479 ifsin.sin_len = sizeof(ifsin); 480 ifsin.sin_family = AF_INET; 481 482 if (fd = socket(AF_INET, SOCK_STREAM, 0), fd < 0) { 483 warnx("cannot create internet socket"); 484 return 2; 485 } 486 } 487 488 TimedOut = 0; 489 if (TimeoutVal) { 490 act.sa_handler = Timeout; 491 sigemptyset(&act.sa_mask); 492 act.sa_flags = 0; 493 sigaction(SIGALRM, &act, &oact); 494 alarm(TimeoutVal); 495 } 496 497 if (connect(fd, sock, socksz) < 0) { 498 if (TimeoutVal) { 499 save_errno = errno; 500 alarm(0); 501 sigaction(SIGALRM, &oact, 0); 502 errno = save_errno; 503 } 504 if (TimedOut) 505 warnx("timeout: cannot connect to socket %s", argv[arg]); 506 else { 507 if (errno) 508 warn("cannot connect to socket %s", argv[arg]); 509 else 510 warnx("cannot connect to socket %s", argv[arg]); 511 } 512 close(fd); 513 return 3; 514 } 515 516 if (TimeoutVal) { 517 alarm(0); 518 sigaction(SIGALRM, &oact, 0); 519 } 520 521 len = 0; 522 Command[sizeof(Command)-1] = '\0'; 523 for (arg++; arg < argc; arg++) { 524 if (len && len < sizeof(Command)-1) 525 strcpy(Command+len++, " "); 526 strncpy(Command+len, argv[arg], sizeof(Command)-len-1); 527 len += strlen(Command+len); 528 } 529 530 switch (Receive(fd, verbose | REC_PASSWD)) { 531 case 1: 532 fprintf(stderr, "Password incorrect\n"); 533 break; 534 535 case 0: 536 passwd = NULL; 537 if (len == 0) { 538 struct thread_data td; 539 const char *env; 540 int size; 541#ifdef __NetBSD__ 542 HistEvent hev = { 0, "" }; 543#endif 544 545 td.hist = history_init(); 546 if ((env = getenv("EL_SIZE"))) { 547 size = atoi(env); 548 if (size < 0) 549 size = 20; 550 } else 551 size = 20; 552#ifdef __NetBSD__ 553 history(td.hist, &hev, H_SETSIZE, size); 554 td.edit = el_init("pppctl", stdin, stdout, stderr); 555#else 556 history(td.hist, H_EVENT, size); 557 td.edit = el_init("pppctl", stdin, stdout); 558#endif 559 el_source(td.edit, NULL); 560 el_set(td.edit, EL_PROMPT, GetPrompt); 561 if ((env = getenv("EL_EDITOR"))) { 562 if (!strcmp(env, "vi")) 563 el_set(td.edit, EL_EDITOR, "vi"); 564 else if (!strcmp(env, "emacs")) 565 el_set(td.edit, EL_EDITOR, "emacs"); 566 } 567 el_set(td.edit, EL_SIGNAL, 1); 568 el_set(td.edit, EL_HIST, history, (const char *)td.hist); 569 570 td.ppp = fd; 571 td.trm = NULL; 572 573 /* 574 * We create two threads. The Terminal thread does all the 575 * work while the Monitor thread simply tells the Terminal 576 * thread when ``fd'' becomes readable. The telling is done 577 * by sending a SIGUSR1 to the Terminal thread. The 578 * sem_select semaphore is used to prevent the monitor 579 * thread from firing excessive signals at the Terminal 580 * thread (it's abused for exit handling too - see below). 581 * 582 * The Terminal thread never uses td.trm ! 583 */ 584 sem_init(&sem_select, 0, 0); 585 586 pthread_create(&td.trm, NULL, Terminal, &td); 587 pthread_create(&mon, NULL, Monitor, &td); 588 589 /* Wait for the terminal thread to finish */ 590 pthread_join(td.trm, &thread_ret); 591 fprintf(stderr, "Connection closed\n"); 592 593 /* Get rid of the monitor thread by abusing sem_select */ 594 timetogo = 1; 595 close(fd); 596 fd = -1; 597 sem_post(&sem_select); 598 pthread_join(mon, &thread_ret); 599 600 /* Restore our terminal and release resources */ 601 el_end(td.edit); 602 history_end(td.hist); 603 sem_destroy(&sem_select); 604 } else { 605 start = Command; 606 do { 607 next = strchr(start, ';'); 608 while (*start == ' ' || *start == '\t') 609 start++; 610 if (next) 611 *next = '\0'; 612 strcpy(Buffer, start); 613 Buffer[sizeof(Buffer)-2] = '\0'; 614 strcat(Buffer, "\n"); 615 if (verbose) 616 write(1, Buffer, strlen(Buffer)); 617 write(fd, Buffer, strlen(Buffer)); 618 if (Receive(fd, verbose | REC_SHOW) != 0) { 619 fprintf(stderr, "Connection closed\n"); 620 break; 621 } 622 if (next) 623 start = ++next; 624 } while (next && *next); 625 if (verbose) 626 puts(""); 627 } 628 break; 629 630 default: 631 warnx("ppp is not responding"); 632 break; 633 } 634 635 if (fd != -1) 636 close(fd); 637 638 return 0; 639} 640