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