1 /* 2 * Chat -- a program for automatic session establishment (i.e. dial 3 * the phone and log in). 4 * 5 * Standard termination codes: 6 * 0 - successful completion of the script 7 * 1 - invalid argument, expect string too large, etc. 8 * 2 - error on an I/O operation or fatal error condition. 9 * 3 - timeout waiting for a simple string. 10 * 4 - the first string declared as "ABORT" 11 * 5 - the second string declared as "ABORT" 12 * 6 - ... and so on for successive ABORT strings. 13 * 14 * This software is in the public domain. 15 * 16 * ----------------- 17 * added -T and -U option and \T and \U substitution to pass a phone 18 * number into chat script. Two are needed for some ISDN TA applications. 19 * Keith Dart <kdart@cisco.com> 20 * 21 * 22 * Added SAY keyword to send output to stderr. 23 * This allows to turn ECHO OFF and to output specific, user selected, 24 * text to give progress messages. This best works when stderr 25 * exists (i.e.: pppd in nodetach mode). 26 * 27 * Added HANGUP directives to allow for us to be called 28 * back. When HANGUP is set to NO, chat will not hangup at HUP signal. 29 * We rely on timeouts in that case. 30 * 31 * Added CLR_ABORT to clear previously set ABORT string. This has been 32 * dictated by the HANGUP above as "NO CARRIER" (for example) must be 33 * an ABORT condition until we know the other host is going to close 34 * the connection for call back. As soon as we have completed the 35 * first stage of the call back sequence, "NO CARRIER" is a valid, non 36 * fatal string. As soon as we got called back (probably get "CONNECT"), 37 * we should re-arm the ABORT "NO CARRIER". Hence the CLR_ABORT command. 38 * Note that CLR_ABORT packs the abort_strings[] array so that we do not 39 * have unused entries not being reclaimed. 40 * 41 * In the same vein as above, added CLR_REPORT keyword. 42 * 43 * Allow for comments. Line starting with '#' are comments and are 44 * ignored. If a '#' is to be expected as the first character, the 45 * expect string must be quoted. 46 * 47 * 48 * Francis Demierre <Francis@SwissMail.Com> 49 * Thu May 15 17:15:40 MET DST 1997 50 * 51 * 52 * Added -r "report file" switch & REPORT keyword. 53 * Robert Geer <bgeer@xmission.com> 54 * 55 * Added -s "use stderr" and -S "don't use syslog" switches. 56 * June 18, 1997 57 * Karl O. Pinc <kop@meme.com> 58 * 59 * 60 * Added -e "echo" switch & ECHO keyword 61 * Dick Streefland <dicks@tasking.nl> 62 * 63 * 64 * Considerable updates and modifications by 65 * Al Longyear <longyear@pobox.com> 66 * Paul Mackerras <paulus@cs.anu.edu.au> 67 * 68 * 69 * The original author is: 70 * 71 * Karl Fox <karl@MorningStar.Com> 72 * Morning Star Technologies, Inc. 73 * 1760 Zollinger Road 74 * Columbus, OH 43221 75 * (614)451-1883 76 * 77 * 78 */ 79 80 #include <sys/cdefs.h> 81 #include <sys/types.h> 82 #include <sys/stat.h> 83 #include <ctype.h> 84 #include <errno.h> 85 #include <fcntl.h> 86 #include <signal.h> 87 #include <stdarg.h> 88 #include <stdio.h> 89 #include <stdlib.h> 90 #include <string.h> 91 #include <syslog.h> 92 #include <termios.h> 93 #include <time.h> 94 #include <unistd.h> 95 96 #define STR_LEN 1024 97 98 #ifndef SIGTYPE 99 #define SIGTYPE void 100 #endif 101 102 #ifndef O_NONBLOCK 103 #define O_NONBLOCK O_NDELAY 104 #endif 105 106 #define MAX_ABORTS 50 107 #define MAX_REPORTS 50 108 #define DEFAULT_CHAT_TIMEOUT 45 109 110 static int echo; 111 static int verbose; 112 static int to_log; 113 static int to_stderr; 114 static int Verbose; 115 static int quiet; 116 static int exit_code; 117 static FILE* report_fp; 118 static char *report_file; 119 static char *chat_file; 120 static char *phone_num; 121 static char *phone_num2; 122 static int timeout = DEFAULT_CHAT_TIMEOUT; 123 124 static char blank[] = ""; 125 126 static int have_tty_parameters; 127 128 #define term_parms struct termios 129 #define get_term_param(param) tcgetattr(0, param) 130 #define set_term_param(param) tcsetattr(0, TCSANOW, param) 131 static struct termios saved_tty_parameters; 132 133 static char *abort_string[MAX_ABORTS], *fail_reason, fail_buffer[50]; 134 static int n_aborts, abort_next, timeout_next, echo_next; 135 static int clear_abort_next; 136 137 static char *report_string[MAX_REPORTS]; 138 static char report_buffer[50]; 139 static int n_reports, report_next, report_gathering; 140 static int clear_report_next; 141 142 static int say_next, hup_next; 143 144 void *dup_mem(void *b, size_t c); 145 void *copy_of(char *s); 146 static void usage(void) __dead2; 147 void chat_logf(const char *fmt, ...); 148 void fatal(int code, const char *fmt, ...); 149 SIGTYPE sigalrm(int signo); 150 SIGTYPE sigint(int signo); 151 SIGTYPE sigterm(int signo); 152 SIGTYPE sighup(int signo); 153 void init(void); 154 void set_tty_parameters(void); 155 void echo_stderr(int); 156 void break_sequence(void); 157 void terminate(int status); 158 void do_file(char *chatfile); 159 int get_string(char *string); 160 int put_string(char *s); 161 int write_char(int c); 162 int put_char(int c); 163 int get_char(void); 164 void chat_send(char *s); 165 char *character(int c); 166 void chat_expect(char *s); 167 char *clean(char *s, int sending); 168 void pack_array(char **array, int end); 169 char *expect_strtok(char *, const char *); 170 int vfmtmsg(char *, int, const char *, va_list); /* vsprintf++ */ 171 172 void * 173 dup_mem(void *b, size_t c) 174 { 175 void *ans = malloc (c); 176 if (!ans) 177 fatal(2, "memory error!"); 178 179 memcpy (ans, b, c); 180 return ans; 181 } 182 183 void * 184 copy_of(char *s) 185 { 186 return dup_mem (s, strlen (s) + 1); 187 } 188 189 /* 190 * chat [-esSvV] [-f chat-file] [-r report-file] [-t timeout] 191 * [-T phone-number] [-U phone-number2] [chat-script] 192 * where chat-script has the form: 193 * [...[[expect[-send[-expect...]] send expect[-send[-expect]] ...]]] 194 * 195 * Perform a UUCP-dialer-like chat script on stdin and stdout. 196 */ 197 int 198 main(int argc, char *argv[]) 199 { 200 int option; 201 202 tzset(); 203 204 while ((option = getopt(argc, argv, "ef:r:sSt:T:U:vV")) != -1) { 205 switch (option) { 206 case 'e': 207 ++echo; 208 break; 209 210 case 'f': 211 if (chat_file != NULL) 212 free(chat_file); 213 chat_file = copy_of(optarg); 214 break; 215 216 case 'r': 217 if (report_fp != NULL) 218 fclose(report_fp); 219 if (report_file != NULL) 220 free(report_file); 221 report_file = copy_of(optarg); 222 report_fp = fopen(report_file, "a"); 223 if (report_fp != NULL) { 224 if (verbose) 225 fprintf(report_fp, "Opening \"%s\"...\n", report_file); 226 } else 227 fatal(2, "cannot open \"%s\" for appending", report_file); 228 break; 229 230 case 's': 231 ++to_stderr; 232 break; 233 234 case 'S': 235 to_log = 0; 236 break; 237 238 case 't': 239 timeout = atoi(optarg); 240 break; 241 242 case 'T': 243 if (phone_num != NULL) 244 free(phone_num); 245 phone_num = copy_of(optarg); 246 break; 247 248 case 'U': 249 if (phone_num2 != NULL) 250 free(phone_num2); 251 phone_num2 = copy_of(optarg); 252 break; 253 254 case 'v': 255 ++verbose; 256 break; 257 258 case 'V': 259 ++Verbose; 260 break; 261 262 default: 263 usage(); 264 break; 265 } 266 } 267 268 argc -= optind; 269 argv += optind; 270 271 /* 272 * Default the report file to the stderr location 273 */ 274 if (report_fp == NULL) 275 report_fp = stderr; 276 277 if (to_log) { 278 openlog("chat", LOG_PID | LOG_NDELAY, LOG_LOCAL2); 279 280 if (verbose) 281 setlogmask(LOG_UPTO(LOG_INFO)); 282 else 283 setlogmask(LOG_UPTO(LOG_WARNING)); 284 } 285 286 if (chat_file != NULL) { 287 if (*argv != NULL) 288 usage(); 289 else { 290 init(); 291 do_file(chat_file); 292 } 293 } else { 294 init(); 295 while (*argv != NULL && argc > 0) { 296 chat_expect(*argv); 297 argv++; 298 argc--; 299 300 if (*argv != NULL && argc > 0) { 301 chat_send(*argv); 302 argv++; 303 argc--; 304 } 305 } 306 } 307 308 terminate(0); 309 return 0; 310 } 311 312 /* 313 * Process a chat script when read from a file. 314 */ 315 316 void 317 do_file(char *chatfile) 318 { 319 int linect, sendflg; 320 char *sp, *arg, quote; 321 char buf [STR_LEN]; 322 FILE *cfp; 323 324 cfp = fopen (chatfile, "r"); 325 if (cfp == NULL) 326 fatal(1, "%s -- open failed: %m", chatfile); 327 328 linect = 0; 329 sendflg = 0; 330 331 while (fgets(buf, STR_LEN, cfp) != NULL) { 332 sp = strchr (buf, '\n'); 333 if (sp) 334 *sp = '\0'; 335 336 linect++; 337 sp = buf; 338 339 /* lines starting with '#' are comments. If a real '#' 340 is to be expected, it should be quoted .... */ 341 if ( *sp == '#' ) 342 continue; 343 344 while (*sp != '\0') { 345 if (*sp == ' ' || *sp == '\t') { 346 ++sp; 347 continue; 348 } 349 350 if (*sp == '"' || *sp == '\'') { 351 quote = *sp++; 352 arg = sp; 353 while (*sp != quote) { 354 if (*sp == '\0') 355 fatal(1, "unterminated quote (line %d)", linect); 356 357 if (*sp++ == '\\') { 358 if (*sp != '\0') 359 ++sp; 360 } 361 } 362 } 363 else { 364 arg = sp; 365 while (*sp != '\0' && *sp != ' ' && *sp != '\t') 366 ++sp; 367 } 368 369 if (*sp != '\0') 370 *sp++ = '\0'; 371 372 if (sendflg) 373 chat_send (arg); 374 else 375 chat_expect (arg); 376 sendflg = !sendflg; 377 } 378 } 379 fclose (cfp); 380 } 381 382 /* 383 * We got an error parsing the command line. 384 */ 385 static void 386 usage(void) 387 { 388 fprintf(stderr, 389 "Usage: chat [-esSvV] [-f chat-file] [-r report-file] [-t timeout]\n" 390 " [-T phone-number] [-U phone-number2] [chat-script]\n" 391 "where chat-script has the form:\n" 392 " [...[[expect[-send[-expect...]] send expect[-send[-expect]] ...]]]\n"); 393 exit(1); 394 } 395 396 /* 397 * Send a message to syslog and/or stderr. 398 */ 399 void 400 chat_logf(const char *fmt, ...) 401 { 402 char line[1024]; 403 va_list args; 404 405 va_start(args, fmt); 406 vfmtmsg(line, sizeof(line), fmt, args); 407 va_end(args); 408 if (to_log) 409 syslog(LOG_INFO, "%s", line); 410 if (to_stderr) 411 fprintf(stderr, "%s\n", line); 412 } 413 414 /* 415 * Print an error message and terminate. 416 */ 417 418 void 419 fatal(int code, const char *fmt, ...) 420 { 421 char line[1024]; 422 va_list args; 423 424 va_start(args, fmt); 425 vfmtmsg(line, sizeof(line), fmt, args); 426 va_end(args); 427 if (to_log) 428 syslog(LOG_ERR, "%s", line); 429 if (to_stderr) 430 fprintf(stderr, "%s\n", line); 431 terminate(code); 432 } 433 434 static int alarmed; 435 436 SIGTYPE sigalrm(int signo __unused) 437 { 438 int flags; 439 440 alarm(1); 441 alarmed = 1; /* Reset alarm to avoid race window */ 442 signal(SIGALRM, sigalrm); /* that can cause hanging in read() */ 443 444 if ((flags = fcntl(0, F_GETFL, 0)) == -1) 445 fatal(2, "Can't get file mode flags on stdin: %m"); 446 447 if (fcntl(0, F_SETFL, flags | O_NONBLOCK) == -1) 448 fatal(2, "Can't set file mode flags on stdin: %m"); 449 450 if (verbose) 451 chat_logf("alarm"); 452 } 453 454 SIGTYPE sigint(int signo __unused) 455 { 456 fatal(2, "SIGINT"); 457 } 458 459 SIGTYPE sigterm(int signo __unused) 460 { 461 fatal(2, "SIGTERM"); 462 } 463 464 SIGTYPE sighup(int signo __unused) 465 { 466 fatal(2, "SIGHUP"); 467 } 468 469 void init(void) 470 { 471 signal(SIGINT, sigint); 472 signal(SIGTERM, sigterm); 473 signal(SIGHUP, sighup); 474 475 set_tty_parameters(); 476 signal(SIGALRM, sigalrm); 477 alarm(0); 478 alarmed = 0; 479 } 480 481 void set_tty_parameters(void) 482 { 483 #if defined(get_term_param) 484 term_parms t; 485 486 if (get_term_param (&t) < 0) 487 fatal(2, "Can't get terminal parameters: %m"); 488 489 saved_tty_parameters = t; 490 have_tty_parameters = 1; 491 492 t.c_iflag |= IGNBRK | ISTRIP | IGNPAR; 493 t.c_oflag = 0; 494 t.c_lflag = 0; 495 t.c_cc[VERASE] = 496 t.c_cc[VKILL] = 0; 497 t.c_cc[VMIN] = 1; 498 t.c_cc[VTIME] = 0; 499 500 if (set_term_param (&t) < 0) 501 fatal(2, "Can't set terminal parameters: %m"); 502 #endif 503 } 504 505 void break_sequence(void) 506 { 507 tcsendbreak (0, 0); 508 } 509 510 void terminate(int status) 511 { 512 echo_stderr(-1); 513 if (report_file != (char *) 0 && report_fp != (FILE *) NULL) { 514 /* 515 * Allow the last of the report string to be gathered before we terminate. 516 */ 517 if (report_gathering) { 518 int c; 519 size_t rep_len; 520 521 rep_len = strlen(report_buffer); 522 while (rep_len + 1 < sizeof(report_buffer)) { 523 alarm(1); 524 c = get_char(); 525 alarm(0); 526 if (c < 0 || iscntrl(c)) 527 break; 528 report_buffer[rep_len] = c; 529 ++rep_len; 530 } 531 report_buffer[rep_len] = 0; 532 fprintf (report_fp, "chat: %s\n", report_buffer); 533 } 534 if (verbose) 535 fprintf (report_fp, "Closing \"%s\".\n", report_file); 536 fclose (report_fp); 537 report_fp = (FILE *) NULL; 538 } 539 540 #if defined(get_term_param) 541 if (have_tty_parameters) { 542 if (set_term_param (&saved_tty_parameters) < 0) 543 fatal(2, "Can't restore terminal parameters: %m"); 544 } 545 #endif 546 547 exit(status); 548 } 549 550 /* 551 * 'Clean up' this string. 552 */ 553 char * 554 clean(char *s, int sending) 555 { 556 char temp[STR_LEN], cur_chr; 557 char *s1, *phchar; 558 int add_return = sending; 559 #define isoctal(chr) (((chr) >= '0') && ((chr) <= '7')) 560 561 s1 = temp; 562 /* Don't overflow buffer, leave room for chars we append later */ 563 while (*s && s1 - temp < (off_t)(sizeof(temp) - 2 - add_return)) { 564 cur_chr = *s++; 565 if (cur_chr == '^') { 566 cur_chr = *s++; 567 if (cur_chr == '\0') { 568 *s1++ = '^'; 569 break; 570 } 571 cur_chr &= 0x1F; 572 if (cur_chr != 0) { 573 *s1++ = cur_chr; 574 } 575 continue; 576 } 577 578 if (cur_chr != '\\') { 579 *s1++ = cur_chr; 580 continue; 581 } 582 583 cur_chr = *s++; 584 if (cur_chr == '\0') { 585 if (sending) { 586 *s1++ = '\\'; 587 *s1++ = '\\'; 588 } 589 break; 590 } 591 592 switch (cur_chr) { 593 case 'b': 594 *s1++ = '\b'; 595 break; 596 597 case 'c': 598 if (sending && *s == '\0') 599 add_return = 0; 600 else 601 *s1++ = cur_chr; 602 break; 603 604 case '\\': 605 case 'K': 606 case 'p': 607 case 'd': 608 if (sending) 609 *s1++ = '\\'; 610 611 *s1++ = cur_chr; 612 break; 613 614 case 'T': 615 if (sending && phone_num) { 616 for ( phchar = phone_num; *phchar != '\0'; phchar++) 617 *s1++ = *phchar; 618 } 619 else { 620 *s1++ = '\\'; 621 *s1++ = 'T'; 622 } 623 break; 624 625 case 'U': 626 if (sending && phone_num2) { 627 for ( phchar = phone_num2; *phchar != '\0'; phchar++) 628 *s1++ = *phchar; 629 } 630 else { 631 *s1++ = '\\'; 632 *s1++ = 'U'; 633 } 634 break; 635 636 case 'q': 637 quiet = 1; 638 break; 639 640 case 'r': 641 *s1++ = '\r'; 642 break; 643 644 case 'n': 645 *s1++ = '\n'; 646 break; 647 648 case 's': 649 *s1++ = ' '; 650 break; 651 652 case 't': 653 *s1++ = '\t'; 654 break; 655 656 case 'N': 657 if (sending) { 658 *s1++ = '\\'; 659 *s1++ = '\0'; 660 } 661 else 662 *s1++ = 'N'; 663 break; 664 665 default: 666 if (isoctal (cur_chr)) { 667 cur_chr &= 0x07; 668 if (isoctal (*s)) { 669 cur_chr <<= 3; 670 cur_chr |= *s++ - '0'; 671 if (isoctal (*s)) { 672 cur_chr <<= 3; 673 cur_chr |= *s++ - '0'; 674 } 675 } 676 677 if (cur_chr != 0 || sending) { 678 if (sending && (cur_chr == '\\' || cur_chr == 0)) 679 *s1++ = '\\'; 680 *s1++ = cur_chr; 681 } 682 break; 683 } 684 685 if (sending) 686 *s1++ = '\\'; 687 *s1++ = cur_chr; 688 break; 689 } 690 } 691 692 if (add_return) 693 *s1++ = '\r'; 694 695 *s1++ = '\0'; /* guarantee closure */ 696 *s1++ = '\0'; /* terminate the string */ 697 return dup_mem (temp, (size_t) (s1 - temp)); /* may have embedded nuls */ 698 } 699 700 /* 701 * A modified version of 'strtok'. This version skips \ sequences. 702 */ 703 704 char * 705 expect_strtok (char *s, const char *term) 706 { 707 static char *str = blank; 708 int escape_flag = 0; 709 char *result; 710 711 /* 712 * If a string was specified then do initial processing. 713 */ 714 if (s) 715 str = s; 716 717 /* 718 * If this is the escape flag then reset it and ignore the character. 719 */ 720 if (*str) 721 result = str; 722 else 723 result = (char *) 0; 724 725 while (*str) { 726 if (escape_flag) { 727 escape_flag = 0; 728 ++str; 729 continue; 730 } 731 732 if (*str == '\\') { 733 ++str; 734 escape_flag = 1; 735 continue; 736 } 737 738 /* 739 * If this is not in the termination string, continue. 740 */ 741 if (strchr (term, *str) == (char *) 0) { 742 ++str; 743 continue; 744 } 745 746 /* 747 * This is the terminator. Mark the end of the string and stop. 748 */ 749 *str++ = '\0'; 750 break; 751 } 752 return (result); 753 } 754 755 /* 756 * Process the expect string 757 */ 758 759 void 760 chat_expect(char *s) 761 { 762 char *expect; 763 char *reply; 764 765 if (strcmp(s, "HANGUP") == 0) { 766 ++hup_next; 767 return; 768 } 769 770 if (strcmp(s, "ABORT") == 0) { 771 ++abort_next; 772 return; 773 } 774 775 if (strcmp(s, "CLR_ABORT") == 0) { 776 ++clear_abort_next; 777 return; 778 } 779 780 if (strcmp(s, "REPORT") == 0) { 781 ++report_next; 782 return; 783 } 784 785 if (strcmp(s, "CLR_REPORT") == 0) { 786 ++clear_report_next; 787 return; 788 } 789 790 if (strcmp(s, "TIMEOUT") == 0) { 791 ++timeout_next; 792 return; 793 } 794 795 if (strcmp(s, "ECHO") == 0) { 796 ++echo_next; 797 return; 798 } 799 800 if (strcmp(s, "SAY") == 0) { 801 ++say_next; 802 return; 803 } 804 805 /* 806 * Fetch the expect and reply string. 807 */ 808 for (;;) { 809 expect = expect_strtok (s, "-"); 810 s = (char *) 0; 811 812 if (expect == (char *) 0) 813 return; 814 815 reply = expect_strtok (s, "-"); 816 817 /* 818 * Handle the expect string. If successful then exit. 819 */ 820 if (get_string (expect)) 821 return; 822 823 /* 824 * If there is a sub-reply string then send it. Otherwise any condition 825 * is terminal. 826 */ 827 if (reply == (char *) 0 || exit_code != 3) 828 break; 829 830 chat_send (reply); 831 } 832 833 /* 834 * The expectation did not occur. This is terminal. 835 */ 836 if (fail_reason) 837 chat_logf("Failed (%s)", fail_reason); 838 else 839 chat_logf("Failed"); 840 terminate(exit_code); 841 } 842 843 /* 844 * Translate the input character to the appropriate string for printing 845 * the data. 846 */ 847 848 char * 849 character(int c) 850 { 851 static char string[10]; 852 const char *meta; 853 854 meta = (c & 0x80) ? "M-" : ""; 855 c &= 0x7F; 856 857 if (c < 32) 858 sprintf(string, "%s^%c", meta, (int)c + '@'); 859 else if (c == 127) 860 sprintf(string, "%s^?", meta); 861 else 862 sprintf(string, "%s%c", meta, c); 863 864 return (string); 865 } 866 867 /* 868 * process the reply string 869 */ 870 void 871 chat_send(char *s) 872 { 873 if (say_next) { 874 say_next = 0; 875 s = clean(s,0); 876 write(STDERR_FILENO, s, strlen(s)); 877 free(s); 878 return; 879 } 880 881 if (hup_next) { 882 hup_next = 0; 883 if (strcmp(s, "OFF") == 0) 884 signal(SIGHUP, SIG_IGN); 885 else 886 signal(SIGHUP, sighup); 887 return; 888 } 889 890 if (echo_next) { 891 echo_next = 0; 892 echo = (strcmp(s, "ON") == 0); 893 return; 894 } 895 896 if (abort_next) { 897 char *s1; 898 899 abort_next = 0; 900 901 if (n_aborts >= MAX_ABORTS) 902 fatal(2, "Too many ABORT strings"); 903 904 s1 = clean(s, 0); 905 906 if (strlen(s1) > strlen(s) 907 || strlen(s1) + 1 > sizeof(fail_buffer)) 908 fatal(1, "Illegal or too-long ABORT string ('%v')", s); 909 910 abort_string[n_aborts++] = s1; 911 912 if (verbose) 913 chat_logf("abort on (%v)", s); 914 return; 915 } 916 917 if (clear_abort_next) { 918 char *s1; 919 int i; 920 int old_max; 921 int pack = 0; 922 923 clear_abort_next = 0; 924 925 s1 = clean(s, 0); 926 927 if (strlen(s1) > strlen(s) 928 || strlen(s1) + 1 > sizeof(fail_buffer)) 929 fatal(1, "Illegal or too-long CLR_ABORT string ('%v')", s); 930 931 old_max = n_aborts; 932 for (i=0; i < n_aborts; i++) { 933 if ( strcmp(s1,abort_string[i]) == 0 ) { 934 free(abort_string[i]); 935 abort_string[i] = NULL; 936 pack++; 937 n_aborts--; 938 if (verbose) 939 chat_logf("clear abort on (%v)", s); 940 } 941 } 942 free(s1); 943 if (pack) 944 pack_array(abort_string,old_max); 945 return; 946 } 947 948 if (report_next) { 949 char *s1; 950 951 report_next = 0; 952 if (n_reports >= MAX_REPORTS) 953 fatal(2, "Too many REPORT strings"); 954 955 s1 = clean(s, 0); 956 957 if (strlen(s1) > strlen(s) || strlen(s1) > sizeof fail_buffer - 1) 958 fatal(1, "Illegal or too-long REPORT string ('%v')", s); 959 960 report_string[n_reports++] = s1; 961 962 if (verbose) 963 chat_logf("report (%v)", s); 964 return; 965 } 966 967 if (clear_report_next) { 968 char *s1; 969 int i; 970 int old_max; 971 int pack = 0; 972 973 clear_report_next = 0; 974 975 s1 = clean(s, 0); 976 977 if (strlen(s1) > strlen(s) || strlen(s1) > sizeof fail_buffer - 1) 978 fatal(1, "Illegal or too-long REPORT string ('%v')", s); 979 980 old_max = n_reports; 981 for (i=0; i < n_reports; i++) { 982 if ( strcmp(s1,report_string[i]) == 0 ) { 983 free(report_string[i]); 984 report_string[i] = NULL; 985 pack++; 986 n_reports--; 987 if (verbose) 988 chat_logf("clear report (%v)", s); 989 } 990 } 991 free(s1); 992 if (pack) 993 pack_array(report_string,old_max); 994 995 return; 996 } 997 998 if (timeout_next) { 999 timeout_next = 0; 1000 timeout = atoi(s); 1001 1002 if (timeout <= 0) 1003 timeout = DEFAULT_CHAT_TIMEOUT; 1004 1005 if (verbose) 1006 chat_logf("timeout set to %d seconds", timeout); 1007 1008 return; 1009 } 1010 1011 if (strcmp(s, "EOT") == 0) 1012 s = strdup("^D\\c"); 1013 else if (strcmp(s, "BREAK") == 0) 1014 s = strdup("\\K\\c"); 1015 1016 if (!put_string(s)) 1017 fatal(1, "Failed"); 1018 } 1019 1020 int 1021 get_char(void) 1022 { 1023 int status; 1024 char c; 1025 1026 status = read(STDIN_FILENO, &c, 1); 1027 1028 switch (status) { 1029 case 1: 1030 return ((int)c & 0x7F); 1031 1032 default: 1033 chat_logf("warning: read() on stdin returned %d", status); 1034 1035 case -1: 1036 if ((status = fcntl(0, F_GETFL, 0)) == -1) 1037 fatal(2, "Can't get file mode flags on stdin: %m"); 1038 1039 if (fcntl(0, F_SETFL, status & ~O_NONBLOCK) == -1) 1040 fatal(2, "Can't set file mode flags on stdin: %m"); 1041 1042 return (-1); 1043 } 1044 } 1045 1046 int put_char(int c) 1047 { 1048 int status; 1049 char ch = c; 1050 1051 usleep(10000); /* inter-character typing delay (?) */ 1052 1053 status = write(STDOUT_FILENO, &ch, 1); 1054 1055 switch (status) { 1056 case 1: 1057 return (0); 1058 1059 default: 1060 chat_logf("warning: write() on stdout returned %d", status); 1061 1062 case -1: 1063 if ((status = fcntl(0, F_GETFL, 0)) == -1) 1064 fatal(2, "Can't get file mode flags on stdin, %m"); 1065 1066 if (fcntl(0, F_SETFL, status & ~O_NONBLOCK) == -1) 1067 fatal(2, "Can't set file mode flags on stdin: %m"); 1068 1069 return (-1); 1070 } 1071 } 1072 1073 int 1074 write_char(int c) 1075 { 1076 if (alarmed || put_char(c) < 0) { 1077 alarm(0); 1078 alarmed = 0; 1079 1080 if (verbose) { 1081 if (errno == EINTR || errno == EWOULDBLOCK) 1082 chat_logf(" -- write timed out"); 1083 else 1084 chat_logf(" -- write failed: %m"); 1085 } 1086 return (0); 1087 } 1088 return (1); 1089 } 1090 1091 int 1092 put_string(char *s) 1093 { 1094 quiet = 0; 1095 s = clean(s, 1); 1096 1097 if (verbose) 1098 chat_logf("send (%v)", quiet ? "??????" : s); 1099 1100 alarm(timeout); alarmed = 0; 1101 1102 while (*s) { 1103 char c = *s++; 1104 1105 if (c != '\\') { 1106 if (!write_char (c)) 1107 return 0; 1108 continue; 1109 } 1110 1111 c = *s++; 1112 switch (c) { 1113 case 'd': 1114 sleep(1); 1115 break; 1116 1117 case 'K': 1118 break_sequence(); 1119 break; 1120 1121 case 'p': 1122 usleep(10000); /* 1/100th of a second (arg is microseconds) */ 1123 break; 1124 1125 default: 1126 if (!write_char (c)) 1127 return 0; 1128 break; 1129 } 1130 } 1131 1132 alarm(0); 1133 alarmed = 0; 1134 return (1); 1135 } 1136 1137 /* 1138 * Echo a character to stderr. 1139 * When called with -1, a '\n' character is generated when 1140 * the cursor is not at the beginning of a line. 1141 */ 1142 void 1143 echo_stderr(int n) 1144 { 1145 static int need_lf; 1146 char *s; 1147 1148 switch (n) { 1149 case '\r': /* ignore '\r' */ 1150 break; 1151 case -1: 1152 if (need_lf == 0) 1153 break; 1154 /* FALLTHROUGH */ 1155 case '\n': 1156 write(STDERR_FILENO, "\n", 1); 1157 need_lf = 0; 1158 break; 1159 default: 1160 s = character(n); 1161 write(STDERR_FILENO, s, strlen(s)); 1162 need_lf = 1; 1163 break; 1164 } 1165 } 1166 1167 /* 1168 * 'Wait for' this string to appear on this file descriptor. 1169 */ 1170 int 1171 get_string(char *string) 1172 { 1173 char temp[STR_LEN]; 1174 int c; 1175 size_t len, minlen; 1176 char *s = temp, *end = s + STR_LEN; 1177 char *logged = temp; 1178 1179 fail_reason = (char *)0; 1180 1181 if (strlen(string) > STR_LEN) { 1182 chat_logf("expect string is too long"); 1183 exit_code = 1; 1184 return 0; 1185 } 1186 1187 string = clean(string, 0); 1188 len = strlen(string); 1189 minlen = (len > sizeof(fail_buffer)? len: sizeof(fail_buffer)) - 1; 1190 1191 if (verbose) 1192 chat_logf("expect (%v)", string); 1193 1194 if (len == 0) { 1195 if (verbose) 1196 chat_logf("got it"); 1197 return (1); 1198 } 1199 1200 alarm(timeout); 1201 alarmed = 0; 1202 1203 while ( ! alarmed && (c = get_char()) >= 0) { 1204 int n, abort_len, report_len; 1205 1206 if (echo) 1207 echo_stderr(c); 1208 if (verbose && c == '\n') { 1209 if (s == logged) 1210 chat_logf(""); /* blank line */ 1211 else 1212 chat_logf("%0.*v", s - logged, logged); 1213 logged = s + 1; 1214 } 1215 1216 *s++ = c; 1217 1218 if (verbose && s >= logged + 80) { 1219 chat_logf("%0.*v", s - logged, logged); 1220 logged = s; 1221 } 1222 1223 if (Verbose) { 1224 if (c == '\n') 1225 fputc( '\n', stderr ); 1226 else if (c != '\r') 1227 fprintf( stderr, "%s", character(c) ); 1228 } 1229 1230 if (!report_gathering) { 1231 for (n = 0; n < n_reports; ++n) { 1232 if ((report_string[n] != (char*) NULL) && 1233 s - temp >= (report_len = strlen(report_string[n])) && 1234 strncmp(s - report_len, report_string[n], report_len) == 0) { 1235 time_t time_now = time ((time_t*) NULL); 1236 struct tm* tm_now = localtime (&time_now); 1237 1238 strftime (report_buffer, 20, "%b %d %H:%M:%S ", tm_now); 1239 strcat (report_buffer, report_string[n]); 1240 1241 report_string[n] = (char *) NULL; 1242 report_gathering = 1; 1243 break; 1244 } 1245 } 1246 } 1247 else { 1248 if (!iscntrl (c)) { 1249 int rep_len = strlen (report_buffer); 1250 report_buffer[rep_len] = c; 1251 report_buffer[rep_len + 1] = '\0'; 1252 } 1253 else { 1254 report_gathering = 0; 1255 fprintf (report_fp, "chat: %s\n", report_buffer); 1256 } 1257 } 1258 1259 if ((size_t)(s - temp) >= len && 1260 c == string[len - 1] && 1261 strncmp(s - len, string, len) == 0) { 1262 if (verbose) { 1263 if (s > logged) 1264 chat_logf("%0.*v", s - logged, logged); 1265 chat_logf(" -- got it\n"); 1266 } 1267 1268 alarm(0); 1269 alarmed = 0; 1270 return (1); 1271 } 1272 1273 for (n = 0; n < n_aborts; ++n) { 1274 if (s - temp >= (abort_len = strlen(abort_string[n])) && 1275 strncmp(s - abort_len, abort_string[n], abort_len) == 0) { 1276 if (verbose) { 1277 if (s > logged) 1278 chat_logf("%0.*v", s - logged, logged); 1279 chat_logf(" -- failed"); 1280 } 1281 1282 alarm(0); 1283 alarmed = 0; 1284 exit_code = n + 4; 1285 strcpy(fail_reason = fail_buffer, abort_string[n]); 1286 return (0); 1287 } 1288 } 1289 1290 if (s >= end) { 1291 if (logged < s - minlen) { 1292 chat_logf("%0.*v", s - logged, logged); 1293 logged = s; 1294 } 1295 s -= minlen; 1296 memmove(temp, s, minlen); 1297 logged = temp + (logged - s); 1298 s = temp + minlen; 1299 } 1300 1301 if (alarmed && verbose) 1302 chat_logf("warning: alarm synchronization problem"); 1303 } 1304 1305 alarm(0); 1306 1307 exit_code = 3; 1308 alarmed = 0; 1309 return (0); 1310 } 1311 1312 void 1313 pack_array(char **array, int end) 1314 { 1315 int i, j; 1316 1317 for (i = 0; i < end; i++) { 1318 if (array[i] == NULL) { 1319 for (j = i+1; j < end; ++j) 1320 if (array[j] != NULL) 1321 array[i++] = array[j]; 1322 for (; i < end; ++i) 1323 array[i] = NULL; 1324 break; 1325 } 1326 } 1327 } 1328 1329 /* 1330 * vfmtmsg - format a message into a buffer. Like vsprintf except we 1331 * also specify the length of the output buffer, and we handle the 1332 * %m (error message) format. 1333 * Doesn't do floating-point formats. 1334 * Returns the number of chars put into buf. 1335 */ 1336 #define OUTCHAR(c) (buflen > 0? (--buflen, *buf++ = (c)): 0) 1337 1338 int 1339 vfmtmsg(char *buf, int buflen, const char *fmt, va_list args) 1340 { 1341 int c, i, n; 1342 int width, prec, fillch; 1343 int base, len, neg, quoted; 1344 unsigned long val = 0; 1345 char *str, *buf0; 1346 const char *f; 1347 unsigned char *p; 1348 char num[32]; 1349 static char hexchars[] = "0123456789abcdef"; 1350 1351 buf0 = buf; 1352 --buflen; 1353 while (buflen > 0) { 1354 for (f = fmt; *f != '%' && *f != 0; ++f) 1355 ; 1356 if (f > fmt) { 1357 len = f - fmt; 1358 if (len > buflen) 1359 len = buflen; 1360 memcpy(buf, fmt, len); 1361 buf += len; 1362 buflen -= len; 1363 fmt = f; 1364 } 1365 if (*fmt == 0) 1366 break; 1367 c = *++fmt; 1368 width = prec = 0; 1369 fillch = ' '; 1370 if (c == '0') { 1371 fillch = '0'; 1372 c = *++fmt; 1373 } 1374 if (c == '*') { 1375 width = va_arg(args, int); 1376 c = *++fmt; 1377 } else { 1378 while (isdigit(c)) { 1379 width = width * 10 + c - '0'; 1380 c = *++fmt; 1381 } 1382 } 1383 if (c == '.') { 1384 c = *++fmt; 1385 if (c == '*') { 1386 prec = va_arg(args, int); 1387 c = *++fmt; 1388 } else { 1389 while (isdigit(c)) { 1390 prec = prec * 10 + c - '0'; 1391 c = *++fmt; 1392 } 1393 } 1394 } 1395 str = NULL; 1396 base = 0; 1397 neg = 0; 1398 ++fmt; 1399 switch (c) { 1400 case 'd': 1401 i = va_arg(args, int); 1402 if (i < 0) { 1403 neg = 1; 1404 val = -i; 1405 } else 1406 val = i; 1407 base = 10; 1408 break; 1409 case 'o': 1410 val = va_arg(args, unsigned int); 1411 base = 8; 1412 break; 1413 case 'x': 1414 val = va_arg(args, unsigned int); 1415 base = 16; 1416 break; 1417 case 'p': 1418 val = (unsigned long) va_arg(args, void *); 1419 base = 16; 1420 neg = 2; 1421 break; 1422 case 's': 1423 str = va_arg(args, char *); 1424 break; 1425 case 'c': 1426 num[0] = va_arg(args, int); 1427 num[1] = 0; 1428 str = num; 1429 break; 1430 case 'm': 1431 str = strerror(errno); 1432 break; 1433 case 'v': /* "visible" string */ 1434 case 'q': /* quoted string */ 1435 quoted = c == 'q'; 1436 p = va_arg(args, unsigned char *); 1437 if (fillch == '0' && prec > 0) { 1438 n = prec; 1439 } else { 1440 n = strlen((char *)p); 1441 if (prec > 0 && prec < n) 1442 n = prec; 1443 } 1444 while (n > 0 && buflen > 0) { 1445 c = *p++; 1446 --n; 1447 if (!quoted && c >= 0x80) { 1448 OUTCHAR('M'); 1449 OUTCHAR('-'); 1450 c -= 0x80; 1451 } 1452 if (quoted && (c == '"' || c == '\\')) 1453 OUTCHAR('\\'); 1454 if (c < 0x20 || (0x7f <= c && c < 0xa0)) { 1455 if (quoted) { 1456 OUTCHAR('\\'); 1457 switch (c) { 1458 case '\t': OUTCHAR('t'); break; 1459 case '\n': OUTCHAR('n'); break; 1460 case '\b': OUTCHAR('b'); break; 1461 case '\f': OUTCHAR('f'); break; 1462 default: 1463 OUTCHAR('x'); 1464 OUTCHAR(hexchars[c >> 4]); 1465 OUTCHAR(hexchars[c & 0xf]); 1466 } 1467 } else { 1468 if (c == '\t') 1469 OUTCHAR(c); 1470 else { 1471 OUTCHAR('^'); 1472 OUTCHAR(c ^ 0x40); 1473 } 1474 } 1475 } else 1476 OUTCHAR(c); 1477 } 1478 continue; 1479 default: 1480 *buf++ = '%'; 1481 if (c != '%') 1482 --fmt; /* so %z outputs %z etc. */ 1483 --buflen; 1484 continue; 1485 } 1486 if (base != 0) { 1487 str = num + sizeof(num); 1488 *--str = 0; 1489 while (str > num + neg) { 1490 *--str = hexchars[val % base]; 1491 val = val / base; 1492 if (--prec <= 0 && val == 0) 1493 break; 1494 } 1495 switch (neg) { 1496 case 1: 1497 *--str = '-'; 1498 break; 1499 case 2: 1500 *--str = 'x'; 1501 *--str = '0'; 1502 break; 1503 } 1504 len = num + sizeof(num) - 1 - str; 1505 } else { 1506 len = strlen(str); 1507 if (prec > 0 && len > prec) 1508 len = prec; 1509 } 1510 if (width > 0) { 1511 if (width > buflen) 1512 width = buflen; 1513 if ((n = width - len) > 0) { 1514 buflen -= n; 1515 for (; n > 0; --n) 1516 *buf++ = fillch; 1517 } 1518 } 1519 if (len > buflen) 1520 len = buflen; 1521 memcpy(buf, str, len); 1522 buf += len; 1523 buflen -= len; 1524 } 1525 *buf = 0; 1526 return buf - buf0; 1527 } 1528