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