1 /* $OpenBSD: utilities.c,v 1.17 2014/07/20 08:56:47 guenther Exp $ */ 2 /* $NetBSD: utilities.c,v 1.5 1996/02/28 21:04:21 thorpej Exp $ */ 3 4 /* 5 * Copyright (c) 1988, 1993 6 * The Regents of the University of California. All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 3. Neither the name of the University nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 * SUCH DAMAGE. 31 */ 32 33 /* these three defines affect the behavior of <arpa/telnet.h> */ 34 #define TELOPTS 35 #define TELCMDS 36 #define SLC_NAMES 37 38 #include "telnet_locl.h" 39 40 #include <arpa/telnet.h> 41 #include <ctype.h> 42 #include <limits.h> 43 #include <poll.h> 44 #include <stdlib.h> 45 #include <string.h> 46 47 static FILE *NetTrace = NULL; 48 int prettydump; 49 50 /* 51 * upcase() 52 * 53 * Upcase (in place) the argument. 54 */ 55 56 void 57 upcase(char *argument) 58 { 59 int c; 60 61 while ((c = *argument) != '\0') 62 *argument++ = toupper((unsigned char)c); 63 } 64 65 /* 66 * The following are routines used to print out debugging information. 67 */ 68 69 unsigned char NetTraceFile[PATH_MAX] = "(standard output)"; 70 71 void 72 SetNetTrace(file) 73 char *file; 74 { 75 if (NetTrace && NetTrace != stdout) 76 fclose(NetTrace); 77 if (file && (strcmp(file, "-") != 0)) { 78 NetTrace = fopen(file, "w"); 79 if (NetTrace) { 80 strlcpy((char *)NetTraceFile, file, sizeof(NetTraceFile)); 81 return; 82 } 83 fprintf(stderr, "Cannot open %s.\n", file); 84 } 85 NetTrace = stdout; 86 strlcpy((char *)NetTraceFile, "(standard output)", sizeof(NetTraceFile)); 87 } 88 89 void 90 Dump(direction, buffer, length) 91 char direction; 92 unsigned char *buffer; 93 int length; 94 { 95 # define BYTES_PER_LINE 32 96 # define min(x,y) ((x<y)? x:y) 97 unsigned char *pThis; 98 int offset; 99 100 offset = 0; 101 102 while (length) { 103 /* print one line */ 104 fprintf(NetTrace, "%c 0x%x\t", direction, offset); 105 pThis = buffer; 106 if (prettydump) { 107 buffer = buffer + min(length, BYTES_PER_LINE/2); 108 while (pThis < buffer) { 109 fprintf(NetTrace, "%c%.2x", 110 (((*pThis)&0xff) == 0xff) ? '*' : ' ', 111 (*pThis)&0xff); 112 pThis++; 113 } 114 length -= BYTES_PER_LINE/2; 115 offset += BYTES_PER_LINE/2; 116 } else { 117 buffer = buffer + min(length, BYTES_PER_LINE); 118 while (pThis < buffer) { 119 fprintf(NetTrace, "%.2x", (*pThis)&0xff); 120 pThis++; 121 } 122 length -= BYTES_PER_LINE; 123 offset += BYTES_PER_LINE; 124 } 125 if (NetTrace == stdout) { 126 fprintf(NetTrace, "\r\n"); 127 } else { 128 fprintf(NetTrace, "\n"); 129 } 130 if (length < 0) { 131 fflush(NetTrace); 132 return; 133 } 134 /* find next unique line */ 135 } 136 fflush(NetTrace); 137 } 138 139 140 void 141 printoption(direction, cmd, option) 142 char *direction; 143 int cmd, option; 144 { 145 if (!showoptions) 146 return; 147 if (cmd == IAC) { 148 if (TELCMD_OK(option)) 149 fprintf(NetTrace, "%s IAC %s", direction, TELCMD(option)); 150 else 151 fprintf(NetTrace, "%s IAC %d", direction, option); 152 } else { 153 char *fmt; 154 fmt = (cmd == WILL) ? "WILL" : (cmd == WONT) ? "WONT" : 155 (cmd == DO) ? "DO" : (cmd == DONT) ? "DONT" : 0; 156 if (fmt) { 157 fprintf(NetTrace, "%s %s ", direction, fmt); 158 if (TELOPT_OK(option)) 159 fprintf(NetTrace, "%s", TELOPT(option)); 160 else if (option == TELOPT_EXOPL) 161 fprintf(NetTrace, "EXOPL"); 162 else 163 fprintf(NetTrace, "%d", option); 164 } else 165 fprintf(NetTrace, "%s %d %d", direction, cmd, option); 166 } 167 if (NetTrace == stdout) { 168 fprintf(NetTrace, "\r\n"); 169 fflush(NetTrace); 170 } else { 171 fprintf(NetTrace, "\n"); 172 } 173 return; 174 } 175 176 void 177 optionstatus() 178 { 179 int i; 180 extern char will_wont_resp[], do_dont_resp[]; 181 182 for (i = 0; i < 256; i++) { 183 if (do_dont_resp[i]) { 184 if (TELOPT_OK(i)) 185 printf("resp DO_DONT %s: %d\n", TELOPT(i), do_dont_resp[i]); 186 else if (TELCMD_OK(i)) 187 printf("resp DO_DONT %s: %d\n", TELCMD(i), do_dont_resp[i]); 188 else 189 printf("resp DO_DONT %d: %d\n", i, 190 do_dont_resp[i]); 191 if (my_want_state_is_do(i)) { 192 if (TELOPT_OK(i)) 193 printf("want DO %s\n", TELOPT(i)); 194 else if (TELCMD_OK(i)) 195 printf("want DO %s\n", TELCMD(i)); 196 else 197 printf("want DO %d\n", i); 198 } else { 199 if (TELOPT_OK(i)) 200 printf("want DONT %s\n", TELOPT(i)); 201 else if (TELCMD_OK(i)) 202 printf("want DONT %s\n", TELCMD(i)); 203 else 204 printf("want DONT %d\n", i); 205 } 206 } else { 207 if (my_state_is_do(i)) { 208 if (TELOPT_OK(i)) 209 printf(" DO %s\n", TELOPT(i)); 210 else if (TELCMD_OK(i)) 211 printf(" DO %s\n", TELCMD(i)); 212 else 213 printf(" DO %d\n", i); 214 } 215 } 216 if (will_wont_resp[i]) { 217 if (TELOPT_OK(i)) 218 printf("resp WILL_WONT %s: %d\n", TELOPT(i), will_wont_resp[i]); 219 else if (TELCMD_OK(i)) 220 printf("resp WILL_WONT %s: %d\n", TELCMD(i), will_wont_resp[i]); 221 else 222 printf("resp WILL_WONT %d: %d\n", 223 i, will_wont_resp[i]); 224 if (my_want_state_is_will(i)) { 225 if (TELOPT_OK(i)) 226 printf("want WILL %s\n", TELOPT(i)); 227 else if (TELCMD_OK(i)) 228 printf("want WILL %s\n", TELCMD(i)); 229 else 230 printf("want WILL %d\n", i); 231 } else { 232 if (TELOPT_OK(i)) 233 printf("want WONT %s\n", TELOPT(i)); 234 else if (TELCMD_OK(i)) 235 printf("want WONT %s\n", TELCMD(i)); 236 else 237 printf("want WONT %d\n", i); 238 } 239 } else { 240 if (my_state_is_will(i)) { 241 if (TELOPT_OK(i)) 242 printf(" WILL %s\n", TELOPT(i)); 243 else if (TELCMD_OK(i)) 244 printf(" WILL %s\n", TELCMD(i)); 245 else 246 printf(" WILL %d\n", i); 247 } 248 } 249 } 250 251 } 252 253 void 254 printsub(direction, pointer, length) 255 char direction; /* '<' or '>' */ 256 unsigned char *pointer; /* where suboption data sits */ 257 int length; /* length of suboption data */ 258 { 259 int i; 260 extern int want_status_response; 261 262 if (showoptions || direction == 0 || 263 (want_status_response && (pointer[0] == TELOPT_STATUS))) { 264 if (direction) { 265 fprintf(NetTrace, "%s IAC SB ", 266 (direction == '<')? "RCVD":"SENT"); 267 if (length >= 3) { 268 int j; 269 270 i = pointer[length-2]; 271 j = pointer[length-1]; 272 273 if (i != IAC || j != SE) { 274 fprintf(NetTrace, "(terminated by "); 275 if (TELOPT_OK(i)) 276 fprintf(NetTrace, "%s ", TELOPT(i)); 277 else if (TELCMD_OK(i)) 278 fprintf(NetTrace, "%s ", TELCMD(i)); 279 else 280 fprintf(NetTrace, "%d ", i); 281 if (TELOPT_OK(j)) 282 fprintf(NetTrace, "%s", TELOPT(j)); 283 else if (TELCMD_OK(j)) 284 fprintf(NetTrace, "%s", TELCMD(j)); 285 else 286 fprintf(NetTrace, "%d", j); 287 fprintf(NetTrace, ", not IAC SE!) "); 288 } 289 } 290 length -= 2; 291 } 292 if (length < 1) { 293 fprintf(NetTrace, "(Empty suboption??\?)"); 294 if (NetTrace == stdout) 295 fflush(NetTrace); 296 return; 297 } 298 switch (pointer[0]) { 299 case TELOPT_TTYPE: 300 fprintf(NetTrace, "TERMINAL-TYPE "); 301 switch (pointer[1]) { 302 case TELQUAL_IS: 303 fprintf(NetTrace, "IS \"%.*s\"", length-2, (char *)pointer+2); 304 break; 305 case TELQUAL_SEND: 306 fprintf(NetTrace, "SEND"); 307 break; 308 default: 309 fprintf(NetTrace, 310 "- unknown qualifier %d (0x%x).", 311 pointer[1], pointer[1]); 312 } 313 break; 314 case TELOPT_TSPEED: 315 fprintf(NetTrace, "TERMINAL-SPEED"); 316 if (length < 2) { 317 fprintf(NetTrace, " (empty suboption??\?)"); 318 break; 319 } 320 switch (pointer[1]) { 321 case TELQUAL_IS: 322 fprintf(NetTrace, " IS "); 323 fprintf(NetTrace, "%.*s", length-2, (char *)pointer+2); 324 break; 325 default: 326 if (pointer[1] == 1) 327 fprintf(NetTrace, " SEND"); 328 else 329 fprintf(NetTrace, " %d (unknown)", pointer[1]); 330 for (i = 2; i < length; i++) 331 fprintf(NetTrace, " ?%d?", pointer[i]); 332 break; 333 } 334 break; 335 336 case TELOPT_LFLOW: 337 fprintf(NetTrace, "TOGGLE-FLOW-CONTROL"); 338 if (length < 2) { 339 fprintf(NetTrace, " (empty suboption??\?)"); 340 break; 341 } 342 switch (pointer[1]) { 343 case LFLOW_OFF: 344 fprintf(NetTrace, " OFF"); break; 345 case LFLOW_ON: 346 fprintf(NetTrace, " ON"); break; 347 case LFLOW_RESTART_ANY: 348 fprintf(NetTrace, " RESTART-ANY"); break; 349 case LFLOW_RESTART_XON: 350 fprintf(NetTrace, " RESTART-XON"); break; 351 default: 352 fprintf(NetTrace, " %d (unknown)", pointer[1]); 353 } 354 for (i = 2; i < length; i++) 355 fprintf(NetTrace, " ?%d?", pointer[i]); 356 break; 357 358 case TELOPT_NAWS: 359 fprintf(NetTrace, "NAWS"); 360 if (length < 2) { 361 fprintf(NetTrace, " (empty suboption??\?)"); 362 break; 363 } 364 if (length == 2) { 365 fprintf(NetTrace, " ?%d?", pointer[1]); 366 break; 367 } 368 fprintf(NetTrace, " %d %d (%d)", 369 pointer[1], pointer[2], 370 (int)((((unsigned int)pointer[1])<<8)|((unsigned int)pointer[2]))); 371 if (length == 4) { 372 fprintf(NetTrace, " ?%d?", pointer[3]); 373 break; 374 } 375 fprintf(NetTrace, " %d %d (%d)", 376 pointer[3], pointer[4], 377 (int)((((unsigned int)pointer[3])<<8)|((unsigned int)pointer[4]))); 378 for (i = 5; i < length; i++) 379 fprintf(NetTrace, " ?%d?", pointer[i]); 380 break; 381 382 case TELOPT_LINEMODE: 383 fprintf(NetTrace, "LINEMODE "); 384 if (length < 2) { 385 fprintf(NetTrace, " (empty suboption??\?)"); 386 break; 387 } 388 switch (pointer[1]) { 389 case WILL: 390 fprintf(NetTrace, "WILL "); 391 goto common; 392 case WONT: 393 fprintf(NetTrace, "WONT "); 394 goto common; 395 case DO: 396 fprintf(NetTrace, "DO "); 397 goto common; 398 case DONT: 399 fprintf(NetTrace, "DONT "); 400 common: 401 if (length < 3) { 402 fprintf(NetTrace, "(no option??\?)"); 403 break; 404 } 405 switch (pointer[2]) { 406 case LM_FORWARDMASK: 407 fprintf(NetTrace, "Forward Mask"); 408 for (i = 3; i < length; i++) 409 fprintf(NetTrace, " %x", pointer[i]); 410 break; 411 default: 412 fprintf(NetTrace, "%d (unknown)", pointer[2]); 413 for (i = 3; i < length; i++) 414 fprintf(NetTrace, " %d", pointer[i]); 415 break; 416 } 417 break; 418 419 case LM_SLC: 420 fprintf(NetTrace, "SLC"); 421 for (i = 2; i < length - 2; i += 3) { 422 if (SLC_NAME_OK(pointer[i+SLC_FUNC])) 423 fprintf(NetTrace, " %s", SLC_NAME(pointer[i+SLC_FUNC])); 424 else 425 fprintf(NetTrace, " %d", pointer[i+SLC_FUNC]); 426 switch (pointer[i+SLC_FLAGS]&SLC_LEVELBITS) { 427 case SLC_NOSUPPORT: 428 fprintf(NetTrace, " NOSUPPORT"); break; 429 case SLC_CANTCHANGE: 430 fprintf(NetTrace, " CANTCHANGE"); break; 431 case SLC_VARIABLE: 432 fprintf(NetTrace, " VARIABLE"); break; 433 case SLC_DEFAULT: 434 fprintf(NetTrace, " DEFAULT"); break; 435 } 436 fprintf(NetTrace, "%s%s%s", 437 pointer[i+SLC_FLAGS]&SLC_ACK ? "|ACK" : "", 438 pointer[i+SLC_FLAGS]&SLC_FLUSHIN ? "|FLUSHIN" : "", 439 pointer[i+SLC_FLAGS]&SLC_FLUSHOUT ? "|FLUSHOUT" : ""); 440 if (pointer[i+SLC_FLAGS]& ~(SLC_ACK|SLC_FLUSHIN| 441 SLC_FLUSHOUT| SLC_LEVELBITS)) 442 fprintf(NetTrace, "(0x%x)", pointer[i+SLC_FLAGS]); 443 fprintf(NetTrace, " %d;", pointer[i+SLC_VALUE]); 444 if ((pointer[i+SLC_VALUE] == IAC) && 445 (pointer[i+SLC_VALUE+1] == IAC)) 446 i++; 447 } 448 for (; i < length; i++) 449 fprintf(NetTrace, " ?%d?", pointer[i]); 450 break; 451 452 case LM_MODE: 453 fprintf(NetTrace, "MODE "); 454 if (length < 3) { 455 fprintf(NetTrace, "(no mode??\?)"); 456 break; 457 } 458 { 459 char tbuf[64]; 460 snprintf(tbuf, sizeof(tbuf), 461 "%s%s%s%s%s", 462 pointer[2]&MODE_EDIT ? "|EDIT" : "", 463 pointer[2]&MODE_TRAPSIG ? "|TRAPSIG" : "", 464 pointer[2]&MODE_SOFT_TAB ? "|SOFT_TAB" : "", 465 pointer[2]&MODE_LIT_ECHO ? "|LIT_ECHO" : "", 466 pointer[2]&MODE_ACK ? "|ACK" : ""); 467 fprintf(NetTrace, "%s", tbuf[1] ? &tbuf[1] : "0"); 468 } 469 if (pointer[2]&~(MODE_MASK)) 470 fprintf(NetTrace, " (0x%x)", pointer[2]); 471 for (i = 3; i < length; i++) 472 fprintf(NetTrace, " ?0x%x?", pointer[i]); 473 break; 474 default: 475 fprintf(NetTrace, "%d (unknown)", pointer[1]); 476 for (i = 2; i < length; i++) 477 fprintf(NetTrace, " %d", pointer[i]); 478 } 479 break; 480 481 case TELOPT_STATUS: { 482 char *cp; 483 int j, k; 484 485 fprintf(NetTrace, "STATUS"); 486 487 switch (pointer[1]) { 488 default: 489 if (pointer[1] == TELQUAL_SEND) 490 fprintf(NetTrace, " SEND"); 491 else 492 fprintf(NetTrace, " %d (unknown)", pointer[1]); 493 for (i = 2; i < length; i++) 494 fprintf(NetTrace, " ?%d?", pointer[i]); 495 break; 496 case TELQUAL_IS: 497 if (--want_status_response < 0) 498 want_status_response = 0; 499 if (NetTrace == stdout) 500 fprintf(NetTrace, " IS\r\n"); 501 else 502 fprintf(NetTrace, " IS\n"); 503 504 for (i = 2; i < length; i++) { 505 switch(pointer[i]) { 506 case DO: cp = "DO"; goto common2; 507 case DONT: cp = "DONT"; goto common2; 508 case WILL: cp = "WILL"; goto common2; 509 case WONT: cp = "WONT"; goto common2; 510 common2: 511 i++; 512 if (TELOPT_OK((int)pointer[i])) 513 fprintf(NetTrace, " %s %s", cp, TELOPT(pointer[i])); 514 else 515 fprintf(NetTrace, " %s %d", cp, pointer[i]); 516 517 if (NetTrace == stdout) 518 fprintf(NetTrace, "\r\n"); 519 else 520 fprintf(NetTrace, "\n"); 521 break; 522 523 case SB: 524 fprintf(NetTrace, " SB "); 525 i++; 526 j = k = i; 527 while (j < length) { 528 if (pointer[j] == SE) { 529 if (j+1 == length) 530 break; 531 if (pointer[j+1] == SE) 532 j++; 533 else 534 break; 535 } 536 pointer[k++] = pointer[j++]; 537 } 538 printsub(0, &pointer[i], k - i); 539 if (i < length) { 540 fprintf(NetTrace, " SE"); 541 i = j; 542 } else 543 i = j - 1; 544 545 if (NetTrace == stdout) 546 fprintf(NetTrace, "\r\n"); 547 else 548 fprintf(NetTrace, "\n"); 549 550 break; 551 552 default: 553 fprintf(NetTrace, " %d", pointer[i]); 554 break; 555 } 556 } 557 break; 558 } 559 break; 560 } 561 562 case TELOPT_XDISPLOC: 563 fprintf(NetTrace, "X-DISPLAY-LOCATION "); 564 switch (pointer[1]) { 565 case TELQUAL_IS: 566 fprintf(NetTrace, "IS \"%.*s\"", length-2, (char *)pointer+2); 567 break; 568 case TELQUAL_SEND: 569 fprintf(NetTrace, "SEND"); 570 break; 571 default: 572 fprintf(NetTrace, "- unknown qualifier %d (0x%x).", 573 pointer[1], pointer[1]); 574 } 575 break; 576 577 case TELOPT_NEW_ENVIRON: 578 fprintf(NetTrace, "NEW-ENVIRON "); 579 switch (pointer[1]) { 580 case TELQUAL_IS: 581 fprintf(NetTrace, "IS "); 582 goto env_common; 583 case TELQUAL_SEND: 584 fprintf(NetTrace, "SEND "); 585 goto env_common; 586 case TELQUAL_INFO: 587 fprintf(NetTrace, "INFO "); 588 env_common: 589 { 590 int noquote = 2; 591 for (i = 2; i < length; i++ ) { 592 switch (pointer[i]) { 593 case NEW_ENV_VALUE: 594 fprintf(NetTrace, "\" VALUE " + noquote); 595 noquote = 2; 596 break; 597 598 case NEW_ENV_VAR: 599 fprintf(NetTrace, "\" VAR " + noquote); 600 noquote = 2; 601 break; 602 603 case ENV_ESC: 604 fprintf(NetTrace, "\" ESC " + noquote); 605 noquote = 2; 606 break; 607 608 case ENV_USERVAR: 609 fprintf(NetTrace, "\" USERVAR " + noquote); 610 noquote = 2; 611 break; 612 613 default: 614 if (isprint((unsigned char)pointer[i]) && 615 pointer[i] != '"') { 616 if (noquote) { 617 putc('"', NetTrace); 618 noquote = 0; 619 } 620 putc(pointer[i], NetTrace); 621 } else { 622 fprintf(NetTrace, "\" %03o " + noquote, 623 pointer[i]); 624 noquote = 2; 625 } 626 break; 627 } 628 } 629 if (!noquote) 630 putc('"', NetTrace); 631 break; 632 } 633 } 634 break; 635 636 default: 637 if (TELOPT_OK(pointer[0])) 638 fprintf(NetTrace, "%s (unknown)", TELOPT(pointer[0])); 639 else 640 fprintf(NetTrace, "%d (unknown)", pointer[0]); 641 for (i = 1; i < length; i++) 642 fprintf(NetTrace, " %d", pointer[i]); 643 break; 644 } 645 if (direction) { 646 if (NetTrace == stdout) 647 fprintf(NetTrace, "\r\n"); 648 else 649 fprintf(NetTrace, "\n"); 650 } 651 if (NetTrace == stdout) 652 fflush(NetTrace); 653 } 654 } 655 656 /* EmptyTerminal - called to make sure that the terminal buffer is empty. 657 * Note that we consider the buffer to run all the 658 * way to the kernel (thus the poll). 659 */ 660 661 void 662 EmptyTerminal() 663 { 664 struct pollfd pfd[1]; 665 666 pfd[0].fd = tout; 667 pfd[0].events = POLLOUT; 668 669 if (TTYBYTES() == 0) { 670 (void) poll(pfd, 1, -1); /* wait for TTLOWAT */ 671 } else { 672 while (TTYBYTES()) { 673 (void) ttyflush(0); 674 (void) poll(pfd, 1, -1); /* wait for TTLOWAT */ 675 } 676 } 677 } 678 679 void 680 SetForExit() 681 { 682 setconnmode(0); 683 do { 684 (void)telrcv(); /* Process any incoming data */ 685 EmptyTerminal(); 686 } while (ring_full_count(&netiring)); /* While there is any */ 687 setcommandmode(); 688 fflush(stdout); 689 fflush(stderr); 690 setconnmode(0); 691 EmptyTerminal(); /* Flush the path to the tty */ 692 setcommandmode(); 693 } 694 695 void 696 Exit(returnCode) 697 int returnCode; 698 { 699 SetForExit(); 700 exit(returnCode); 701 } 702 703 void 704 ExitString(string, returnCode) 705 char *string; 706 int returnCode; 707 { 708 SetForExit(); 709 fwrite(string, 1, strlen(string), stderr); 710 exit(returnCode); 711 } 712