chat.c revision 31070
1/* 2 * Written by Toshiharu OHNO (tony-o@iij.ad.jp) 3 * 4 * Copyright (C) 1993, Internet Initiative Japan, Inc. All rights reserverd. 5 * 6 * Most of codes are derived from chat.c by Karl Fox (karl@MorningStar.Com). 7 * 8 * Chat -- a program for automatic session establishment (i.e. dial 9 * the phone and log in). 10 * 11 * This software is in the public domain. 12 * 13 * Please send all bug reports, requests for information, etc. to: 14 * 15 * Karl Fox <karl@MorningStar.Com> 16 * Morning Star Technologies, Inc. 17 * 1760 Zollinger Road 18 * Columbus, OH 43221 19 * (614)451-1883 20 * 21 * $Id: chat.c,v 1.37 1997/11/09 06:22:39 brian Exp $ 22 * 23 * TODO: 24 * o Support more UUCP compatible control sequences. 25 * o Dialing shoud not block monitor process. 26 * o Reading modem by select should be unified into main.c 27 */ 28#include <sys/param.h> 29#include <netinet/in.h> 30 31#include <ctype.h> 32#include <errno.h> 33#include <fcntl.h> 34#include <setjmp.h> 35#include <signal.h> 36#include <stdio.h> 37#include <stdlib.h> 38#include <string.h> 39#include <sys/socket.h> 40#include <sys/time.h> 41#include <sys/uio.h> 42#include <sys/wait.h> 43#include <termios.h> 44#include <unistd.h> 45 46#include "mbuf.h" 47#include "log.h" 48#include "defs.h" 49#include "timer.h" 50#include "loadalias.h" 51#include "command.h" 52#include "vars.h" 53#include "chat.h" 54#include "sig.h" 55#include "chat.h" 56#include "modem.h" 57 58#ifndef isblank 59#define isblank(c) ((c) == '\t' || (c) == ' ') 60#endif 61 62 63#define IBSIZE LINE_LEN 64 65static int TimeoutSec; 66static int abort_next, timeout_next; 67static int numaborts; 68static char *AbortStrings[50]; 69static char inbuff[IBSIZE * 2 + 1]; 70 71#define MATCH 1 72#define NOMATCH 0 73#define ABORT -1 74 75static char * 76findblank(char *p, int instring) 77{ 78 if (instring) { 79 while (*p) { 80 if (*p == '\\') { 81 strcpy(p, p + 1); 82 if (!*p) 83 break; 84 } else if (*p == '"') 85 return (p); 86 p++; 87 } 88 } else { 89 while (*p) { 90 if (isblank(*p)) 91 return (p); 92 p++; 93 } 94 } 95 return p; 96} 97 98int 99MakeArgs(char *script, char **pvect, int maxargs) 100{ 101 int nargs, nb; 102 int instring; 103 104 nargs = 0; 105 while (*script) { 106 nb = strspn(script, " \t"); 107 script += nb; 108 if (*script) { 109 if (*script == '"') { 110 instring = 1; 111 script++; 112 if (*script == '\0') 113 break; /* Shouldn't return here. Need to null 114 * terminate below */ 115 } else 116 instring = 0; 117 if (nargs >= maxargs - 1) 118 break; 119 *pvect++ = script; 120 nargs++; 121 script = findblank(script, instring); 122 if (*script) 123 *script++ = '\0'; 124 } 125 } 126 *pvect = NULL; 127 return nargs; 128} 129 130/* 131 * \c don't add a cr 132 * \d Sleep a little (delay 2 seconds 133 * \n Line feed character 134 * \P Auth Key password 135 * \p pause 0.25 sec 136 * \r Carrige return character 137 * \s Space character 138 * \T Telephone number(s) (defined via `set phone') 139 * \t Tab character 140 * \U Auth User 141 */ 142char * 143ExpandString(char *str, char *result, int reslen, int sendmode) 144{ 145 int addcr = 0; 146 char *phone; 147 148 result[--reslen] = '\0'; 149 if (sendmode) 150 addcr = 1; 151 while (*str && reslen > 0) { 152 switch (*str) { 153 case '\\': 154 str++; 155 switch (*str) { 156 case 'c': 157 if (sendmode) 158 addcr = 0; 159 break; 160 case 'd': /* Delay 2 seconds */ 161 nointr_sleep(2); 162 break; 163 case 'p': 164 nointr_usleep(250000); 165 break; /* Pause 0.25 sec */ 166 case 'n': 167 *result++ = '\n'; 168 reslen--; 169 break; 170 case 'r': 171 *result++ = '\r'; 172 reslen--; 173 break; 174 case 's': 175 *result++ = ' '; 176 reslen--; 177 break; 178 case 't': 179 *result++ = '\t'; 180 reslen--; 181 break; 182 case 'P': 183 strncpy(result, VarAuthKey, reslen); 184 reslen -= strlen(result); 185 result += strlen(result); 186 break; 187 case 'T': 188 if (VarAltPhone == NULL) { 189 if (VarNextPhone == NULL) { 190 strcpy(VarPhoneCopy, VarPhoneList); 191 VarNextPhone = VarPhoneCopy; 192 } 193 VarAltPhone = strsep(&VarNextPhone, ":"); 194 } 195 phone = strsep(&VarAltPhone, "|"); 196 strncpy(result, phone, reslen); 197 reslen -= strlen(result); 198 result += strlen(result); 199 if (VarTerm) 200 fprintf(VarTerm, "Phone: %s\n", phone); 201 LogPrintf(LogPHASE, "Phone: %s\n", phone); 202 break; 203 case 'U': 204 strncpy(result, VarAuthName, reslen); 205 reslen -= strlen(result); 206 result += strlen(result); 207 break; 208 default: 209 reslen--; 210 *result++ = *str; 211 break; 212 } 213 if (*str) 214 str++; 215 break; 216 case '^': 217 str++; 218 if (*str) { 219 *result++ = *str++ & 0x1f; 220 reslen--; 221 } 222 break; 223 default: 224 *result++ = *str++; 225 reslen--; 226 break; 227 } 228 } 229 if (--reslen > 0) { 230 if (addcr) 231 *result++ = '\r'; 232 } 233 if (--reslen > 0) 234 *result++ = '\0'; 235 return (result); 236} 237 238#define MAXLOGBUFF LINE_LEN 239static char logbuff[MAXLOGBUFF]; 240static int loglen = 0; 241 242static void 243clear_log() 244{ 245 memset(logbuff, 0, MAXLOGBUFF); 246 loglen = 0; 247} 248 249static void 250flush_log() 251{ 252 if (LogIsKept(LogCONNECT)) 253 LogPrintf(LogCONNECT, "%s\n", logbuff); 254 else if (LogIsKept(LogCARRIER) && strstr(logbuff, "CARRIER")) 255 LogPrintf(LogCARRIER, "%s\n", logbuff); 256 257 clear_log(); 258} 259 260static void 261connect_log(char *str, int single_p) 262{ 263 int space = MAXLOGBUFF - loglen - 1; 264 265 while (space--) { 266 if (*str == '\n') { 267 flush_log(); 268 } else { 269 logbuff[loglen++] = *str; 270 } 271 if (single_p || !*++str) 272 break; 273 } 274 if (!space) 275 flush_log(); 276} 277 278static int 279WaitforString(char *estr) 280{ 281 struct timeval timeout; 282 char *s, *str, ch; 283 char *inp; 284 fd_set rfds; 285 int i, nfds, nb; 286 char buff[IBSIZE]; 287 288 289#ifdef SIGALRM 290 int omask; 291 292 omask = sigblock(sigmask(SIGALRM)); 293#endif 294 clear_log(); 295 (void) ExpandString(estr, buff, sizeof(buff), 0); 296 LogPrintf(LogCHAT, "Wait for (%d): %s --> %s\n", TimeoutSec, estr, buff); 297 str = buff; 298 inp = inbuff; 299 300 if (strlen(str) >= IBSIZE) { 301 str[IBSIZE - 1] = 0; 302 LogPrintf(LogCHAT, "Truncating String to %d character: %s\n", IBSIZE, str); 303 } 304 nfds = modem + 1; 305 s = str; 306 for (;;) { 307 FD_ZERO(&rfds); 308 FD_SET(modem, &rfds); 309 310 /* 311 * Because it is not clear whether select() modifies timeout value, it is 312 * better to initialize timeout values everytime. 313 */ 314 timeout.tv_sec = TimeoutSec; 315 timeout.tv_usec = 0; 316 i = select(nfds, &rfds, NULL, NULL, &timeout); 317#ifdef notdef 318 TimerService(); 319#endif 320 if (i < 0) { 321#ifdef SIGALRM 322 if (errno == EINTR) 323 continue; 324 sigsetmask(omask); 325#endif 326 LogPrintf(LogERROR, "WaitForString: select(): %s\n", strerror(errno)); 327 *inp = 0; 328 return (NOMATCH); 329 } else if (i == 0) { /* Timeout reached! */ 330 *inp = 0; 331 if (inp != inbuff) 332 LogPrintf(LogCHAT, "Got: %s\n", inbuff); 333 LogPrintf(LogCHAT, "Can't get (%d).\n", timeout.tv_sec); 334#ifdef SIGALRM 335 sigsetmask(omask); 336#endif 337 return (NOMATCH); 338 } 339 if (FD_ISSET(modem, &rfds)) { /* got something */ 340 if (DEV_IS_SYNC) { 341 int length; 342 343 if ((length = strlen(inbuff)) > IBSIZE) { 344 /* shuffle down next part */ 345 memcpy(inbuff, &(inbuff[IBSIZE]), IBSIZE + 1); 346 length = strlen(inbuff); 347 } 348 nb = read(modem, &(inbuff[length]), IBSIZE); 349 inbuff[nb + length] = 0; 350 connect_log(inbuff, 0); 351 if (strstr(inbuff, str)) { 352#ifdef SIGALRM 353 sigsetmask(omask); 354#endif 355 flush_log(); 356 return (MATCH); 357 } 358 for (i = 0; i < numaborts; i++) { 359 if (strstr(inbuff, AbortStrings[i])) { 360 LogPrintf(LogCHAT, "Abort: %s\n", AbortStrings[i]); 361#ifdef SIGALRM 362 sigsetmask(omask); 363#endif 364 flush_log(); 365 return (ABORT); 366 } 367 } 368 } else { 369 if (read(modem, &ch, 1) < 0) { 370 LogPrintf(LogERROR, "read error: %s\n", strerror(errno)); 371 *inp = '\0'; 372 return (NOMATCH); 373 } 374 connect_log(&ch, 1); 375 *inp++ = ch; 376 if (ch == *s) { 377 s++; 378 if (*s == '\0') { 379#ifdef SIGALRM 380 sigsetmask(omask); 381#endif 382 *inp = 0; 383 flush_log(); 384 return (MATCH); 385 } 386 } else 387 s = str; 388 if (inp == inbuff + IBSIZE) { 389 memcpy(inbuff, inp - 100, 100); 390 inp = inbuff + 100; 391 } 392 if (s == str) { 393 for (i = 0; i < numaborts; i++) { /* Look for Abort strings */ 394 int len; 395 char *s1; 396 397 s1 = AbortStrings[i]; 398 len = strlen(s1); 399 if ((len <= inp - inbuff) && (strncmp(inp - len, s1, len) == 0)) { 400 LogPrintf(LogCHAT, "Abort: %s\n", s1); 401 *inp = 0; 402#ifdef SIGALRM 403 sigsetmask(omask); 404#endif 405 flush_log(); 406 return (ABORT); 407 } 408 } 409 } 410 } 411 } 412 } 413} 414 415static void 416ExecStr(char *command, char *out) 417{ 418 int pid; 419 int fids[2]; 420 char *vector[20]; 421 int stat, nb; 422 char *cp; 423 char tmp[300]; 424 425 cp = inbuff + strlen(inbuff) - 1; 426 while (cp > inbuff) { 427 if (*cp < ' ' && *cp != '\t') { 428 cp++; 429 break; 430 } 431 cp--; 432 } 433 if (snprintf(tmp, sizeof tmp, "%s %s", command, cp) >= sizeof tmp) { 434 LogPrintf(LogCHAT, "Too long string to ExecStr: \"%s\"\n", command); 435 return; 436 } 437 (void) MakeArgs(tmp, vector, VECSIZE(vector)); 438 439 if (pipe(fids) < 0) { 440 LogPrintf(LogCHAT, "Unable to create pipe in ExecStr: %s\n", 441 strerror(errno)); 442 return; 443 } 444 pid = fork(); 445 if (pid == 0) { 446 TermTimerService(); 447 signal(SIGINT, SIG_DFL); 448 signal(SIGQUIT, SIG_DFL); 449 signal(SIGTERM, SIG_DFL); 450 signal(SIGHUP, SIG_DFL); 451 signal(SIGALRM, SIG_DFL); 452 close(fids[0]); 453 if (dup2(fids[1], 1) < 0) { 454 LogPrintf(LogCHAT, "dup2(fids[1], 1) in ExecStr: %s\n", strerror(errno)); 455 return; 456 } 457 close(fids[1]); 458 nb = open("/dev/tty", O_RDWR); 459 if (dup2(nb, 0) < 0) { 460 LogPrintf(LogCHAT, "dup2(nb, 0) in ExecStr: %s\n", strerror(errno)); 461 return; 462 } 463 setuid(geteuid()); 464 LogPrintf(LogCHAT, "exec: %s\n", command); 465 pid = execvp(command, vector); 466 LogPrintf(LogCHAT, "execvp failed for (%d/%d): %s\n", pid, errno, command); 467 exit(127); 468 } else { 469 close(fids[1]); 470 for (;;) { 471 nb = read(fids[0], out, 1); 472 if (nb <= 0) 473 break; 474 out++; 475 } 476 *out = '\0'; 477 close(fids[0]); 478 close(fids[1]); 479 waitpid(pid, &stat, WNOHANG); 480 } 481} 482 483static void 484SendString(char *str) 485{ 486 char *cp; 487 int on; 488 char buff[LINE_LEN]; 489 490 if (abort_next) { 491 abort_next = 0; 492 ExpandString(str, buff, sizeof(buff), 0); 493 AbortStrings[numaborts++] = strdup(buff); 494 } else if (timeout_next) { 495 timeout_next = 0; 496 TimeoutSec = atoi(str); 497 if (TimeoutSec <= 0) 498 TimeoutSec = 30; 499 } else { 500 if (*str == '!') { 501 (void) ExpandString(str + 1, buff + 2, sizeof(buff) - 2, 0); 502 ExecStr(buff + 2, buff + 2); 503 } else { 504 (void) ExpandString(str, buff + 2, sizeof(buff) - 2, 1); 505 } 506 if (strstr(str, "\\P")) /* Do not log the password itself. */ 507 LogPrintf(LogCHAT, "sending: %s\n", str); 508 else 509 LogPrintf(LogCHAT, "sending: %s\n", buff + 2); 510 cp = buff; 511 if (DEV_IS_SYNC) 512 memcpy(buff, "\377\003", 2); /* Prepend HDLC header */ 513 else 514 cp += 2; 515 on = strlen(cp); 516 write(modem, cp, on); 517 } 518} 519 520static int 521ExpectString(char *str) 522{ 523 char *minus; 524 int state; 525 526 if (strcmp(str, "ABORT") == 0) { 527 ++abort_next; 528 return (MATCH); 529 } 530 if (strcmp(str, "TIMEOUT") == 0) { 531 ++timeout_next; 532 return (MATCH); 533 } 534 LogPrintf(LogCHAT, "Expecting %s\n", str); 535 while (*str) { 536 537 /* 538 * Check whether if string contains sub-send-expect. 539 */ 540 for (minus = str; *minus; minus++) { 541 if (*minus == '-') { 542 if (minus == str || minus[-1] != '\\') 543 break; 544 } 545 } 546 if (*minus == '-') { /* We have sub-send-expect. */ 547 *minus++ = '\0'; 548 state = WaitforString(str); 549 if (state != NOMATCH) 550 return (state); 551 552 /* 553 * Can't get expect string. Sendout send part. 554 */ 555 str = minus; 556 for (minus = str; *minus; minus++) { 557 if (*minus == '-') { 558 if (minus == str || minus[-1] != '\\') 559 break; 560 } 561 } 562 if (*minus == '-') { 563 *minus++ = '\0'; 564 SendString(str); 565 str = minus; 566 } else { 567 SendString(str); 568 return (MATCH); 569 } 570 } else { 571 572 /* 573 * Simple case. Wait for string. 574 */ 575 return (WaitforString(str)); 576 } 577 } 578 return (MATCH); 579} 580 581static jmp_buf ChatEnv; 582static void (*oint) (int); 583 584static void 585StopDial(int sig) 586{ 587 LogPrintf(LogPHASE, "DoChat: Caught signal %d, abort connect\n", sig); 588 longjmp(ChatEnv, 1); 589} 590 591int 592DoChat(char *script) 593{ 594 char *vector[40]; 595 char **argv; 596 int argc, n, state; 597 598 if (!script || !*script) 599 return MATCH; 600 601 /* While we're chatting, we want an INT to fail us */ 602 if (setjmp(ChatEnv)) { 603 signal(SIGINT, oint); 604 return (-1); 605 } 606 oint = signal(SIGINT, StopDial); 607 608 timeout_next = abort_next = 0; 609 for (n = 0; AbortStrings[n]; n++) { 610 free(AbortStrings[n]); 611 AbortStrings[n] = NULL; 612 } 613 numaborts = 0; 614 615 memset(vector, '\0', sizeof(vector)); 616 n = MakeArgs(script, vector, VECSIZE(vector)); 617 argc = n; 618 argv = vector; 619 TimeoutSec = 30; 620 while (*argv) { 621 if (strcmp(*argv, "P_ZERO") == 0 || 622 strcmp(*argv, "P_ODD") == 0 || strcmp(*argv, "P_EVEN") == 0) { 623 ChangeParity(*argv++); 624 continue; 625 } 626 state = ExpectString(*argv++); 627 switch (state) { 628 case MATCH: 629 if (*argv) 630 SendString(*argv++); 631 break; 632 case ABORT: 633 case NOMATCH: 634 signal(SIGINT, oint); 635 return (NOMATCH); 636 } 637 } 638 signal(SIGINT, oint); 639 return (MATCH); 640} 641