1 /* 2 * Copyright (c) 1983, 1990 The Regents of the University of California. 3 * All rights reserved. 4 * 5 * %sccs.include.redist.c% 6 */ 7 8 #ifndef lint 9 char copyright[] = 10 "@(#) Copyright (c) 1983, 1990 The Regents of the University of California.\n\ 11 All rights reserved.\n"; 12 #endif /* not lint */ 13 14 #ifndef lint 15 static char sccsid[] = "@(#)rlogin.c 5.30 (Berkeley) 08/23/90"; 16 #endif /* not lint */ 17 18 /* 19 * $Source: mit/rlogin/RCS/rlogin.c,v $ 20 * $Header: mit/rlogin/RCS/rlogin.c,v 5.2 89/07/26 12:11:21 kfall 21 * Exp Locker: kfall $ 22 */ 23 24 /* 25 * rlogin - remote login 26 */ 27 #include <sys/param.h> 28 #include <sys/file.h> 29 #include <sys/socket.h> 30 #include <sys/signal.h> 31 #include <sys/time.h> 32 #include <sys/resource.h> 33 #include <sys/wait.h> 34 35 #include <netinet/in.h> 36 #include <netinet/in_systm.h> 37 #include <netinet/ip.h> 38 #include <netdb.h> 39 40 #include <sgtty.h> 41 #include <setjmp.h> 42 #include <varargs.h> 43 #include <errno.h> 44 #include <pwd.h> 45 #include <stdio.h> 46 #include <unistd.h> 47 #include <string.h> 48 49 #ifdef KERBEROS 50 #include <kerberosIV/des.h> 51 #include <kerberosIV/krb.h> 52 53 CREDENTIALS cred; 54 Key_schedule schedule; 55 int use_kerberos = 1, encrypt; 56 char dst_realm_buf[REALM_SZ], *dest_realm = NULL; 57 extern char *krb_realmofhost(); 58 #endif 59 60 #ifndef TIOCPKT_WINDOW 61 #define TIOCPKT_WINDOW 0x80 62 #endif 63 64 /* concession to Sun */ 65 #ifndef SIGUSR1 66 #define SIGUSR1 30 67 #endif 68 69 extern int errno; 70 int eight, litout, rem; 71 72 int noescape; 73 u_char escapechar = '~'; 74 75 char *speeds[] = { 76 "0", "50", "75", "110", "134", "150", "200", "300", "600", "1200", 77 "1800", "2400", "4800", "9600", "19200", "38400" 78 }; 79 80 #ifdef sun 81 struct winsize { 82 unsigned short ws_row, ws_col; 83 unsigned short ws_xpixel, ws_ypixel; 84 }; 85 #endif 86 struct winsize winsize; 87 88 #ifndef sun 89 #define get_window_size(fd, wp) ioctl(fd, TIOCGWINSZ, wp) 90 #endif 91 92 void exit(); 93 94 main(argc, argv) 95 int argc; 96 char **argv; 97 { 98 extern char *optarg; 99 extern int optind; 100 struct passwd *pw; 101 struct servent *sp; 102 struct sgttyb ttyb; 103 long omask; 104 int argoff, ch, dflag, one, uid; 105 char *host, *p, *user, term[1024]; 106 void lostpeer(); 107 u_char getescape(); 108 char *getenv(); 109 110 argoff = dflag = 0; 111 one = 1; 112 host = user = NULL; 113 114 if (p = rindex(argv[0], '/')) 115 ++p; 116 else 117 p = argv[0]; 118 119 if (strcmp(p, "rlogin")) 120 host = p; 121 122 /* handle "rlogin host flags" */ 123 if (!host && argc > 2 && argv[1][0] != '-') { 124 host = argv[1]; 125 argoff = 1; 126 } 127 128 #ifdef KERBEROS 129 #define OPTIONS "8EKLde:k:l:x" 130 #else 131 #define OPTIONS "8EKLde:l:" 132 #endif 133 while ((ch = getopt(argc - argoff, argv + argoff, OPTIONS)) != EOF) 134 switch(ch) { 135 case '8': 136 eight = 1; 137 break; 138 case 'E': 139 noescape = 1; 140 break; 141 case 'K': 142 #ifdef KERBEROS 143 use_kerberos = 0; 144 #endif 145 break; 146 case 'L': 147 litout = 1; 148 break; 149 case 'd': 150 dflag = 1; 151 break; 152 case 'e': 153 escapechar = getescape(optarg); 154 break; 155 #ifdef KERBEROS 156 case 'k': 157 dest_realm = dst_realm_buf; 158 (void)strncpy(dest_realm, optarg, REALM_SZ); 159 break; 160 #endif 161 case 'l': 162 user = optarg; 163 break; 164 #ifdef KERBEROS 165 case 'x': 166 encrypt = 1; 167 des_set_key(cred.session, schedule); 168 break; 169 #endif 170 case '?': 171 default: 172 usage(); 173 } 174 optind += argoff; 175 argc -= optind; 176 argv += optind; 177 178 /* if haven't gotten a host yet, do so */ 179 if (!host && !(host = *argv++)) 180 usage(); 181 182 if (*argv) 183 usage(); 184 185 if (!(pw = getpwuid(uid = getuid()))) { 186 (void)fprintf(stderr, "rlogin: unknown user id.\n"); 187 exit(1); 188 } 189 if (!user) 190 user = pw->pw_name; 191 192 sp = NULL; 193 #ifdef KERBEROS 194 if (use_kerberos) { 195 sp = getservbyname((encrypt ? "eklogin" : "klogin"), "tcp"); 196 if (sp == NULL) { 197 use_kerberos = 0; 198 warning("can't get entry for %s/tcp service", 199 encrypt ? "eklogin" : "klogin"); 200 } 201 } 202 #endif 203 if (sp == NULL) 204 sp = getservbyname("login", "tcp"); 205 if (sp == NULL) { 206 (void)fprintf(stderr, "rlogin: login/tcp: unknown service.\n"); 207 exit(1); 208 } 209 210 (void)strcpy(term, (p = getenv("TERM")) ? p : "network"); 211 if (ioctl(0, TIOCGETP, &ttyb) == 0) { 212 (void)strcat(term, "/"); 213 (void)strcat(term, speeds[ttyb.sg_ospeed]); 214 } 215 216 (void)get_window_size(0, &winsize); 217 218 (void)signal(SIGPIPE, lostpeer); 219 /* will use SIGUSR1 for window size hack, so hold it off */ 220 omask = sigblock(sigmask(SIGURG) | sigmask(SIGUSR1)); 221 222 #ifdef KERBEROS 223 try_connect: 224 if (use_kerberos) { 225 rem = KSUCCESS; 226 errno = 0; 227 if (dest_realm == NULL) 228 dest_realm = krb_realmofhost(host); 229 230 if (encrypt) 231 rem = krcmd_mutual(&host, sp->s_port, user, term, 0, 232 dest_realm, &cred, schedule); 233 else 234 rem = krcmd(&host, sp->s_port, user, term, 0, 235 dest_realm); 236 if (rem < 0) { 237 use_kerberos = 0; 238 sp = getservbyname("login", "tcp"); 239 if (sp == NULL) { 240 (void)fprintf(stderr, 241 "rlogin: unknown service login/tcp.\n"); 242 exit(1); 243 } 244 if (errno == ECONNREFUSED) 245 warning("remote host doesn't support Kerberos"); 246 if (errno == ENOENT) 247 warning("can't provide Kerberos auth data"); 248 goto try_connect; 249 } 250 } else { 251 if (encrypt) { 252 (void)fprintf(stderr, 253 "rlogin: the -x flag requires Kerberos authentication.\n"); 254 exit(1); 255 } 256 rem = rcmd(&host, sp->s_port, pw->pw_name, user, term, 0); 257 } 258 #else 259 rem = rcmd(&host, sp->s_port, pw->pw_name, user, term, 0); 260 #endif 261 262 if (rem < 0) 263 exit(1); 264 265 if (dflag && 266 setsockopt(rem, SOL_SOCKET, SO_DEBUG, &one, sizeof(one)) < 0) 267 (void)fprintf(stderr, "rlogin: setsockopt: %s.\n", 268 strerror(errno)); 269 one = IPTOS_LOWDELAY; 270 if (setsockopt(rem, IPPROTO_IP, IP_TOS, (char *)&one, sizeof(int)) < 0) 271 perror("rlogin: setsockopt TOS (ignored)"); 272 273 (void)setuid(uid); 274 doit(omask); 275 /*NOTREACHED*/ 276 } 277 278 int child, defflags, deflflags, tabflag; 279 char deferase, defkill; 280 struct tchars deftc; 281 struct ltchars defltc; 282 struct tchars notc = { -1, -1, -1, -1, -1, -1 }; 283 struct ltchars noltc = { -1, -1, -1, -1, -1, -1 }; 284 285 doit(omask) 286 long omask; 287 { 288 struct sgttyb sb; 289 void catch_child(), copytochild(), exit(), writeroob(); 290 291 (void)ioctl(0, TIOCGETP, (char *)&sb); 292 defflags = sb.sg_flags; 293 tabflag = defflags & TBDELAY; 294 defflags &= ECHO | CRMOD; 295 deferase = sb.sg_erase; 296 defkill = sb.sg_kill; 297 (void)ioctl(0, TIOCLGET, (char *)&deflflags); 298 (void)ioctl(0, TIOCGETC, (char *)&deftc); 299 notc.t_startc = deftc.t_startc; 300 notc.t_stopc = deftc.t_stopc; 301 (void)ioctl(0, TIOCGLTC, (char *)&defltc); 302 (void)signal(SIGINT, SIG_IGN); 303 setsignal(SIGHUP, exit); 304 setsignal(SIGQUIT, exit); 305 child = fork(); 306 if (child == -1) { 307 (void)fprintf(stderr, "rlogin: fork: %s.\n", strerror(errno)); 308 done(1); 309 } 310 if (child == 0) { 311 mode(1); 312 if (reader(omask) == 0) { 313 msg("connection closed."); 314 exit(0); 315 } 316 sleep(1); 317 msg("\007connection closed."); 318 exit(1); 319 } 320 321 /* 322 * We may still own the socket, and may have a pending SIGURG (or might 323 * receive one soon) that we really want to send to the reader. Set a 324 * trap that simply copies such signals to the child. 325 */ 326 (void)signal(SIGURG, copytochild); 327 (void)signal(SIGUSR1, writeroob); 328 (void)sigsetmask(omask); 329 (void)signal(SIGCHLD, catch_child); 330 writer(); 331 msg("closed connection."); 332 done(0); 333 } 334 335 /* trap a signal, unless it is being ignored. */ 336 setsignal(sig, act) 337 int sig; 338 void (*act)(); 339 { 340 int omask = sigblock(sigmask(sig)); 341 342 if (signal(sig, act) == SIG_IGN) 343 (void)signal(sig, SIG_IGN); 344 (void)sigsetmask(omask); 345 } 346 347 done(status) 348 int status; 349 { 350 int w; 351 352 mode(0); 353 if (child > 0) { 354 /* make sure catch_child does not snap it up */ 355 (void)signal(SIGCHLD, SIG_DFL); 356 if (kill(child, SIGKILL) >= 0) 357 while ((w = wait((union wait *)0)) > 0 && w != child); 358 } 359 exit(status); 360 } 361 362 int dosigwinch; 363 364 /* 365 * This is called when the reader process gets the out-of-band (urgent) 366 * request to turn on the window-changing protocol. 367 */ 368 void 369 writeroob() 370 { 371 void sigwinch(); 372 373 if (dosigwinch == 0) { 374 sendwindow(); 375 (void)signal(SIGWINCH, sigwinch); 376 } 377 dosigwinch = 1; 378 } 379 380 void 381 catch_child() 382 { 383 union wait status; 384 int pid; 385 386 for (;;) { 387 pid = wait3(&status, WNOHANG|WUNTRACED, (struct rusage *)0); 388 if (pid == 0) 389 return; 390 /* if the child (reader) dies, just quit */ 391 if (pid < 0 || pid == child && !WIFSTOPPED(status)) 392 done((int)(status.w_termsig | status.w_retcode)); 393 } 394 /* NOTREACHED */ 395 } 396 397 /* 398 * writer: write to remote: 0 -> line. 399 * ~. terminate 400 * ~^Z suspend rlogin process. 401 * ~<delayed-suspend char> suspend rlogin process, but leave reader alone. 402 */ 403 writer() 404 { 405 register int bol, local, n; 406 char c; 407 408 bol = 1; /* beginning of line */ 409 local = 0; 410 for (;;) { 411 n = read(STDIN_FILENO, &c, 1); 412 if (n <= 0) { 413 if (n < 0 && errno == EINTR) 414 continue; 415 break; 416 } 417 /* 418 * If we're at the beginning of the line and recognize a 419 * command character, then we echo locally. Otherwise, 420 * characters are echo'd remotely. If the command character 421 * is doubled, this acts as a force and local echo is 422 * suppressed. 423 */ 424 if (bol) { 425 bol = 0; 426 if (!noescape && c == escapechar) { 427 local = 1; 428 continue; 429 } 430 } else if (local) { 431 local = 0; 432 if (c == '.' || c == deftc.t_eofc) { 433 echo(c); 434 break; 435 } 436 if (c == defltc.t_suspc || c == defltc.t_dsuspc) { 437 bol = 1; 438 echo(c); 439 stop(c); 440 continue; 441 } 442 if (c != escapechar) 443 #ifdef KERBEROS 444 if (encrypt) 445 (void)des_write(rem, &escapechar, 1); 446 else 447 #endif 448 (void)write(rem, &escapechar, 1); 449 } 450 451 #ifdef KERBEROS 452 if (encrypt) { 453 if (des_write(rem, &c, 1) == 0) { 454 msg("line gone"); 455 break; 456 } 457 } else 458 #endif 459 if (write(rem, &c, 1) == 0) { 460 msg("line gone"); 461 break; 462 } 463 bol = c == defkill || c == deftc.t_eofc || 464 c == deftc.t_intrc || c == defltc.t_suspc || 465 c == '\r' || c == '\n'; 466 } 467 } 468 469 echo(c) 470 register char c; 471 { 472 register char *p; 473 char buf[8]; 474 475 p = buf; 476 c &= 0177; 477 *p++ = escapechar; 478 if (c < ' ') { 479 *p++ = '^'; 480 *p++ = c + '@'; 481 } else if (c == 0177) { 482 *p++ = '^'; 483 *p++ = '?'; 484 } else 485 *p++ = c; 486 *p++ = '\r'; 487 *p++ = '\n'; 488 (void)write(STDOUT_FILENO, buf, p - buf); 489 } 490 491 stop(cmdc) 492 char cmdc; 493 { 494 mode(0); 495 (void)signal(SIGCHLD, SIG_IGN); 496 (void)kill(cmdc == defltc.t_suspc ? 0 : getpid(), SIGTSTP); 497 (void)signal(SIGCHLD, catch_child); 498 mode(1); 499 sigwinch(); /* check for size changes */ 500 } 501 502 void 503 sigwinch() 504 { 505 struct winsize ws; 506 507 if (dosigwinch && get_window_size(0, &ws) == 0 && 508 bcmp(&ws, &winsize, sizeof(ws))) { 509 winsize = ws; 510 sendwindow(); 511 } 512 } 513 514 /* 515 * Send the window size to the server via the magic escape 516 */ 517 sendwindow() 518 { 519 struct winsize *wp; 520 char obuf[4 + sizeof (struct winsize)]; 521 522 wp = (struct winsize *)(obuf+4); 523 obuf[0] = 0377; 524 obuf[1] = 0377; 525 obuf[2] = 's'; 526 obuf[3] = 's'; 527 wp->ws_row = htons(winsize.ws_row); 528 wp->ws_col = htons(winsize.ws_col); 529 wp->ws_xpixel = htons(winsize.ws_xpixel); 530 wp->ws_ypixel = htons(winsize.ws_ypixel); 531 532 #ifdef KERBEROS 533 if(encrypt) 534 (void)des_write(rem, obuf, sizeof(obuf)); 535 else 536 #endif 537 (void)write(rem, obuf, sizeof(obuf)); 538 } 539 540 /* 541 * reader: read from remote: line -> 1 542 */ 543 #define READING 1 544 #define WRITING 2 545 546 jmp_buf rcvtop; 547 int ppid, rcvcnt, rcvstate; 548 char rcvbuf[8 * 1024]; 549 550 void 551 oob() 552 { 553 struct sgttyb sb; 554 int atmark, n, out, rcvd; 555 char waste[BUFSIZ], mark; 556 557 out = O_RDWR; 558 rcvd = 0; 559 while (recv(rem, &mark, 1, MSG_OOB) < 0) 560 switch (errno) { 561 case EWOULDBLOCK: 562 /* 563 * Urgent data not here yet. It may not be possible 564 * to send it yet if we are blocked for output and 565 * our input buffer is full. 566 */ 567 if (rcvcnt < sizeof(rcvbuf)) { 568 n = read(rem, rcvbuf + rcvcnt, 569 sizeof(rcvbuf) - rcvcnt); 570 if (n <= 0) 571 return; 572 rcvd += n; 573 } else { 574 n = read(rem, waste, sizeof(waste)); 575 if (n <= 0) 576 return; 577 } 578 continue; 579 default: 580 return; 581 } 582 if (mark & TIOCPKT_WINDOW) { 583 /* Let server know about window size changes */ 584 (void)kill(ppid, SIGUSR1); 585 } 586 if (!eight && (mark & TIOCPKT_NOSTOP)) { 587 (void)ioctl(0, TIOCGETP, (char *)&sb); 588 sb.sg_flags &= ~CBREAK; 589 sb.sg_flags |= RAW; 590 (void)ioctl(0, TIOCSETN, (char *)&sb); 591 notc.t_stopc = -1; 592 notc.t_startc = -1; 593 (void)ioctl(0, TIOCSETC, (char *)¬c); 594 } 595 if (!eight && (mark & TIOCPKT_DOSTOP)) { 596 (void)ioctl(0, TIOCGETP, (char *)&sb); 597 sb.sg_flags &= ~RAW; 598 sb.sg_flags |= CBREAK; 599 (void)ioctl(0, TIOCSETN, (char *)&sb); 600 notc.t_stopc = deftc.t_stopc; 601 notc.t_startc = deftc.t_startc; 602 (void)ioctl(0, TIOCSETC, (char *)¬c); 603 } 604 if (mark & TIOCPKT_FLUSHWRITE) { 605 (void)ioctl(1, TIOCFLUSH, (char *)&out); 606 for (;;) { 607 if (ioctl(rem, SIOCATMARK, &atmark) < 0) { 608 (void)fprintf(stderr, "rlogin: ioctl: %s.\n", 609 strerror(errno)); 610 break; 611 } 612 if (atmark) 613 break; 614 n = read(rem, waste, sizeof (waste)); 615 if (n <= 0) 616 break; 617 } 618 /* 619 * Don't want any pending data to be output, so clear the recv 620 * buffer. If we were hanging on a write when interrupted, 621 * don't want it to restart. If we were reading, restart 622 * anyway. 623 */ 624 rcvcnt = 0; 625 longjmp(rcvtop, 1); 626 } 627 628 /* oob does not do FLUSHREAD (alas!) */ 629 630 /* 631 * If we filled the receive buffer while a read was pending, longjmp 632 * to the top to restart appropriately. Don't abort a pending write, 633 * however, or we won't know how much was written. 634 */ 635 if (rcvd && rcvstate == READING) 636 longjmp(rcvtop, 1); 637 } 638 639 /* reader: read from remote: line -> 1 */ 640 reader(omask) 641 int omask; 642 { 643 void oob(); 644 645 #if !defined(BSD) || BSD < 43 646 int pid = -getpid(); 647 #else 648 int pid = getpid(); 649 #endif 650 int n, remaining; 651 char *bufp = rcvbuf; 652 653 (void)signal(SIGTTOU, SIG_IGN); 654 (void)signal(SIGURG, oob); 655 ppid = getppid(); 656 (void)fcntl(rem, F_SETOWN, pid); 657 (void)setjmp(rcvtop); 658 (void)sigsetmask(omask); 659 for (;;) { 660 while ((remaining = rcvcnt - (bufp - rcvbuf)) > 0) { 661 rcvstate = WRITING; 662 n = write(STDOUT_FILENO, bufp, remaining); 663 if (n < 0) { 664 if (errno != EINTR) 665 return(-1); 666 continue; 667 } 668 bufp += n; 669 } 670 bufp = rcvbuf; 671 rcvcnt = 0; 672 rcvstate = READING; 673 674 #ifdef KERBEROS 675 if (encrypt) 676 rcvcnt = des_read(rem, rcvbuf, sizeof(rcvbuf)); 677 else 678 #endif 679 rcvcnt = read(rem, rcvbuf, sizeof (rcvbuf)); 680 if (rcvcnt == 0) 681 return (0); 682 if (rcvcnt < 0) { 683 if (errno == EINTR) 684 continue; 685 (void)fprintf(stderr, "rlogin: read: %s.\n", 686 strerror(errno)); 687 return(-1); 688 } 689 } 690 } 691 692 mode(f) 693 { 694 struct ltchars *ltc; 695 struct sgttyb sb; 696 struct tchars *tc; 697 int lflags; 698 699 (void)ioctl(0, TIOCGETP, (char *)&sb); 700 (void)ioctl(0, TIOCLGET, (char *)&lflags); 701 switch(f) { 702 case 0: 703 sb.sg_flags &= ~(CBREAK|RAW|TBDELAY); 704 sb.sg_flags |= defflags|tabflag; 705 tc = &deftc; 706 ltc = &defltc; 707 sb.sg_kill = defkill; 708 sb.sg_erase = deferase; 709 lflags = deflflags; 710 break; 711 case 1: 712 sb.sg_flags |= (eight ? RAW : CBREAK); 713 sb.sg_flags &= ~defflags; 714 /* preserve tab delays, but turn off XTABS */ 715 if ((sb.sg_flags & TBDELAY) == XTABS) 716 sb.sg_flags &= ~TBDELAY; 717 tc = ¬c; 718 ltc = &noltc; 719 sb.sg_kill = sb.sg_erase = -1; 720 if (litout) 721 lflags |= LLITOUT; 722 break; 723 default: 724 return; 725 } 726 (void)ioctl(0, TIOCSLTC, (char *)ltc); 727 (void)ioctl(0, TIOCSETC, (char *)tc); 728 (void)ioctl(0, TIOCSETN, (char *)&sb); 729 (void)ioctl(0, TIOCLSET, (char *)&lflags); 730 } 731 732 void 733 lostpeer() 734 { 735 (void)signal(SIGPIPE, SIG_IGN); 736 msg("\007connection closed."); 737 done(1); 738 } 739 740 /* copy SIGURGs to the child process. */ 741 void 742 copytochild() 743 { 744 (void)kill(child, SIGURG); 745 } 746 747 msg(str) 748 char *str; 749 { 750 (void)fprintf(stderr, "rlogin: %s\r\n", str); 751 } 752 753 #ifdef KERBEROS 754 /* VARARGS */ 755 warning(va_alist) 756 va_dcl 757 { 758 va_list ap; 759 char *fmt; 760 761 (void)fprintf(stderr, "rlogin: warning, using standard rlogin: "); 762 va_start(ap); 763 fmt = va_arg(ap, char *); 764 vfprintf(stderr, fmt, ap); 765 va_end(ap); 766 (void)fprintf(stderr, ".\n"); 767 } 768 #endif 769 770 usage() 771 { 772 (void)fprintf(stderr, 773 "usage: rlogin [ -%s]%s[-e char] [ -l username ] host\n", 774 #ifdef KERBEROS 775 "8ELx", " [-k realm] "); 776 #else 777 "8EL", " "); 778 #endif 779 exit(1); 780 } 781 782 /* 783 * The following routine provides compatibility (such as it is) between 4.2BSD 784 * Suns and others. Suns have only a `ttysize', so we convert it to a winsize. 785 */ 786 #ifdef sun 787 get_window_size(fd, wp) 788 int fd; 789 struct winsize *wp; 790 { 791 struct ttysize ts; 792 int error; 793 794 if ((error = ioctl(0, TIOCGSIZE, &ts)) != 0) 795 return(error); 796 wp->ws_row = ts.ts_lines; 797 wp->ws_col = ts.ts_cols; 798 wp->ws_xpixel = 0; 799 wp->ws_ypixel = 0; 800 return(0); 801 } 802 #endif 803 804 u_char 805 getescape(p) 806 register char *p; 807 { 808 long val; 809 int len; 810 811 if ((len = strlen(p)) == 1) /* use any single char, including '\' */ 812 return((u_char)*p); 813 /* otherwise, \nnn */ 814 if (*p == '\\' && len >= 2 && len <= 4) { 815 val = strtol(++p, (char **)NULL, 8); 816 for (;;) { 817 if (!*++p) 818 return((u_char)val); 819 if (*p < '0' || *p > '8') 820 break; 821 } 822 } 823 msg("illegal option value -- e"); 824 usage(); 825 /* NOTREACHED */ 826 } 827