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 * $FreeBSD: src/usr.bin/chat/chat.c,v 1.21 2003/10/31 06:22:03 kientzle Exp $ 79 */ 80 81 #include <sys/cdefs.h> 82 #include <sys/types.h> 83 #include <sys/stat.h> 84 #include <ctype.h> 85 #include <errno.h> 86 #include <fcntl.h> 87 #include <signal.h> 88 #include <stdarg.h> 89 #include <stdio.h> 90 #include <stdlib.h> 91 #include <string.h> 92 #include <syslog.h> 93 #include <termios.h> 94 #include <time.h> 95 #include <unistd.h> 96 97 #define STR_LEN 1024 98 99 #ifndef SIGTYPE 100 #define SIGTYPE void 101 #endif 102 103 #ifndef O_NONBLOCK 104 #define O_NONBLOCK O_NDELAY 105 #endif 106 107 #define MAX_ABORTS 50 108 #define MAX_REPORTS 50 109 #define DEFAULT_CHAT_TIMEOUT 45 110 111 int echo = 0; 112 int verbose = 0; 113 int to_log = 1; 114 int to_stderr = 0; 115 int Verbose = 0; 116 int quiet = 0; 117 int exit_code = 0; 118 FILE* report_fp = NULL; 119 char *report_file = NULL; 120 char *chat_file = NULL; 121 char *phone_num = NULL; 122 char *phone_num2 = NULL; 123 int timeout = DEFAULT_CHAT_TIMEOUT; 124 125 static char blank[] = ""; 126 127 int have_tty_parameters = 0; 128 129 #define term_parms struct termios 130 #define get_term_param(param) tcgetattr(0, param) 131 #define set_term_param(param) tcsetattr(0, TCSANOW, param) 132 struct termios saved_tty_parameters; 133 134 char *abort_string[MAX_ABORTS], *fail_reason = NULL, 135 fail_buffer[50]; 136 int n_aborts = 0, abort_next = 0, timeout_next = 0, echo_next = 0; 137 int clear_abort_next = 0; 138 139 char *report_string[MAX_REPORTS] ; 140 char report_buffer[50] ; 141 int n_reports = 0, report_next = 0, report_gathering = 0 ; 142 int clear_report_next = 0; 143 144 int say_next = 0, hup_next = 0; 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 char line[1024]; 399 400 /* 401 * Send a message to syslog and/or stderr. 402 */ 403 void 404 chat_logf(const char *fmt, ...) 405 { 406 va_list args; 407 408 va_start(args, fmt); 409 vfmtmsg(line, sizeof(line), fmt, 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 va_list args; 424 425 va_start(args, fmt); 426 vfmtmsg(line, sizeof(line), fmt, 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 int alarmed = 0; 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 != NULL && report_fp != 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 = 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 = NULL; 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) == NULL) { 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 = NULL; 811 812 if (expect == NULL) 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 == NULL || 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, printed = 0; 1175 size_t len, minlen; 1176 char *s = temp, *end = s + STR_LEN; 1177 char *logged = temp; 1178 1179 fail_reason = NULL; 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] != 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 (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] = 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 if (verbose && printed) { 1308 if (alarmed) 1309 chat_logf(" -- read timed out"); 1310 else 1311 chat_logf(" -- read failed: %m"); 1312 } 1313 1314 exit_code = 3; 1315 alarmed = 0; 1316 return (0); 1317 } 1318 1319 void 1320 pack_array(char **array, int end) 1321 { 1322 int i, j; 1323 1324 for (i = 0; i < end; i++) { 1325 if (array[i] == NULL) { 1326 for (j = i+1; j < end; ++j) 1327 if (array[j] != NULL) 1328 array[i++] = array[j]; 1329 for (; i < end; ++i) 1330 array[i] = NULL; 1331 break; 1332 } 1333 } 1334 } 1335 1336 /* 1337 * vfmtmsg - format a message into a buffer. Like vsprintf except we 1338 * also specify the length of the output buffer, and we handle the 1339 * %m (error message) format. 1340 * Doesn't do floating-point formats. 1341 * Returns the number of chars put into buf. 1342 */ 1343 #define OUTCHAR(c) (buflen > 0? (--buflen, *buf++ = (c)): 0) 1344 1345 int 1346 vfmtmsg(char *buf, int buflen, const char *fmt, va_list args) 1347 { 1348 int c, i, n; 1349 int width, prec, fillch; 1350 int base, len, neg, quoted; 1351 unsigned long val = 0; 1352 char *str, *buf0; 1353 const char *f; 1354 unsigned char *p; 1355 char num[32]; 1356 static char hexchars[] = "0123456789abcdef"; 1357 1358 buf0 = buf; 1359 --buflen; 1360 while (buflen > 0) { 1361 for (f = fmt; *f != '%' && *f != 0; ++f) 1362 ; 1363 if (f > fmt) { 1364 len = f - fmt; 1365 if (len > buflen) 1366 len = buflen; 1367 memcpy(buf, fmt, len); 1368 buf += len; 1369 buflen -= len; 1370 fmt = f; 1371 } 1372 if (*fmt == 0) 1373 break; 1374 c = *++fmt; 1375 width = prec = 0; 1376 fillch = ' '; 1377 if (c == '0') { 1378 fillch = '0'; 1379 c = *++fmt; 1380 } 1381 if (c == '*') { 1382 width = va_arg(args, int); 1383 c = *++fmt; 1384 } else { 1385 while (isdigit(c)) { 1386 width = width * 10 + c - '0'; 1387 c = *++fmt; 1388 } 1389 } 1390 if (c == '.') { 1391 c = *++fmt; 1392 if (c == '*') { 1393 prec = va_arg(args, int); 1394 c = *++fmt; 1395 } else { 1396 while (isdigit(c)) { 1397 prec = prec * 10 + c - '0'; 1398 c = *++fmt; 1399 } 1400 } 1401 } 1402 str = NULL; 1403 base = 0; 1404 neg = 0; 1405 ++fmt; 1406 switch (c) { 1407 case 'd': 1408 i = va_arg(args, int); 1409 if (i < 0) { 1410 neg = 1; 1411 val = -i; 1412 } else 1413 val = i; 1414 base = 10; 1415 break; 1416 case 'o': 1417 val = va_arg(args, unsigned int); 1418 base = 8; 1419 break; 1420 case 'x': 1421 val = va_arg(args, unsigned int); 1422 base = 16; 1423 break; 1424 case 'p': 1425 val = (unsigned long) va_arg(args, void *); 1426 base = 16; 1427 neg = 2; 1428 break; 1429 case 's': 1430 str = va_arg(args, char *); 1431 break; 1432 case 'c': 1433 num[0] = va_arg(args, int); 1434 num[1] = 0; 1435 str = num; 1436 break; 1437 case 'm': 1438 str = strerror(errno); 1439 break; 1440 case 'v': /* "visible" string */ 1441 case 'q': /* quoted string */ 1442 quoted = c == 'q'; 1443 p = va_arg(args, unsigned char *); 1444 if (fillch == '0' && prec > 0) { 1445 n = prec; 1446 } else { 1447 n = strlen((char *)p); 1448 if (prec > 0 && prec < n) 1449 n = prec; 1450 } 1451 while (n > 0 && buflen > 0) { 1452 c = *p++; 1453 --n; 1454 if (!quoted && c >= 0x80) { 1455 OUTCHAR('M'); 1456 OUTCHAR('-'); 1457 c -= 0x80; 1458 } 1459 if (quoted && (c == '"' || c == '\\')) 1460 OUTCHAR('\\'); 1461 if (c < 0x20 || (0x7f <= c && c < 0xa0)) { 1462 if (quoted) { 1463 OUTCHAR('\\'); 1464 switch (c) { 1465 case '\t': OUTCHAR('t'); break; 1466 case '\n': OUTCHAR('n'); break; 1467 case '\b': OUTCHAR('b'); break; 1468 case '\f': OUTCHAR('f'); break; 1469 default: 1470 OUTCHAR('x'); 1471 OUTCHAR(hexchars[c >> 4]); 1472 OUTCHAR(hexchars[c & 0xf]); 1473 } 1474 } else { 1475 if (c == '\t') 1476 OUTCHAR(c); 1477 else { 1478 OUTCHAR('^'); 1479 OUTCHAR(c ^ 0x40); 1480 } 1481 } 1482 } else 1483 OUTCHAR(c); 1484 } 1485 continue; 1486 default: 1487 *buf++ = '%'; 1488 if (c != '%') 1489 --fmt; /* so %z outputs %z etc. */ 1490 --buflen; 1491 continue; 1492 } 1493 if (base != 0) { 1494 str = num + sizeof(num); 1495 *--str = 0; 1496 while (str > num + neg) { 1497 *--str = hexchars[val % base]; 1498 val = val / base; 1499 if (--prec <= 0 && val == 0) 1500 break; 1501 } 1502 switch (neg) { 1503 case 1: 1504 *--str = '-'; 1505 break; 1506 case 2: 1507 *--str = 'x'; 1508 *--str = '0'; 1509 break; 1510 } 1511 len = num + sizeof(num) - 1 - str; 1512 } else { 1513 len = strlen(str); 1514 if (prec > 0 && len > prec) 1515 len = prec; 1516 } 1517 if (width > 0) { 1518 if (width > buflen) 1519 width = buflen; 1520 if ((n = width - len) > 0) { 1521 buflen -= n; 1522 for (; n > 0; --n) 1523 *buf++ = fillch; 1524 } 1525 } 1526 if (len > buflen) 1527 len = buflen; 1528 memcpy(buf, str, len); 1529 buf += len; 1530 buflen -= len; 1531 } 1532 *buf = 0; 1533 return buf - buf0; 1534 } 1535