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