chat.c revision 30825
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.35 1997/10/26 01:02:22 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 200 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 200 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 LogPrintf(LogCHAT, "exec: %s\n", command); 464 /* switch back to original privileges */ 465 if (setgid(getgid()) < 0) { 466 LogPrintf(LogCHAT, "setgid: %s\n", strerror(errno)); 467 exit(1); 468 } 469 if (setuid(getuid()) < 0) { 470 LogPrintf(LogCHAT, "setuid: %s\n", strerror(errno)); 471 exit(1); 472 } 473 pid = execvp(command, vector); 474 LogPrintf(LogCHAT, "execvp failed for (%d/%d): %s\n", pid, errno, command); 475 exit(127); 476 } else { 477 close(fids[1]); 478 for (;;) { 479 nb = read(fids[0], out, 1); 480 if (nb <= 0) 481 break; 482 out++; 483 } 484 *out = '\0'; 485 close(fids[0]); 486 close(fids[1]); 487 waitpid(pid, &stat, WNOHANG); 488 } 489} 490 491static void 492SendString(char *str) 493{ 494 char *cp; 495 int on; 496 char buff[200]; 497 498 if (abort_next) { 499 abort_next = 0; 500 ExpandString(str, buff, sizeof(buff), 0); 501 AbortStrings[numaborts++] = strdup(buff); 502 } else if (timeout_next) { 503 timeout_next = 0; 504 TimeoutSec = atoi(str); 505 if (TimeoutSec <= 0) 506 TimeoutSec = 30; 507 } else { 508 if (*str == '!') { 509 (void) ExpandString(str + 1, buff + 2, sizeof(buff) - 2, 0); 510 ExecStr(buff + 2, buff + 2); 511 } else { 512 (void) ExpandString(str, buff + 2, sizeof(buff) - 2, 1); 513 } 514 if (strstr(str, "\\P")) /* Do not log the password itself. */ 515 LogPrintf(LogCHAT, "sending: %s\n", str); 516 else 517 LogPrintf(LogCHAT, "sending: %s\n", buff + 2); 518 cp = buff; 519 if (DEV_IS_SYNC) 520 memcpy(buff, "\377\003", 2); /* Prepend HDLC header */ 521 else 522 cp += 2; 523 on = strlen(cp); 524 write(modem, cp, on); 525 } 526} 527 528static int 529ExpectString(char *str) 530{ 531 char *minus; 532 int state; 533 534 if (strcmp(str, "ABORT") == 0) { 535 ++abort_next; 536 return (MATCH); 537 } 538 if (strcmp(str, "TIMEOUT") == 0) { 539 ++timeout_next; 540 return (MATCH); 541 } 542 LogPrintf(LogCHAT, "Expecting %s\n", str); 543 while (*str) { 544 545 /* 546 * Check whether if string contains sub-send-expect. 547 */ 548 for (minus = str; *minus; minus++) { 549 if (*minus == '-') { 550 if (minus == str || minus[-1] != '\\') 551 break; 552 } 553 } 554 if (*minus == '-') { /* We have sub-send-expect. */ 555 *minus++ = '\0'; 556 state = WaitforString(str); 557 if (state != NOMATCH) 558 return (state); 559 560 /* 561 * Can't get expect string. Sendout send part. 562 */ 563 str = minus; 564 for (minus = str; *minus; minus++) { 565 if (*minus == '-') { 566 if (minus == str || minus[-1] != '\\') 567 break; 568 } 569 } 570 if (*minus == '-') { 571 *minus++ = '\0'; 572 SendString(str); 573 str = minus; 574 } else { 575 SendString(str); 576 return (MATCH); 577 } 578 } else { 579 580 /* 581 * Simple case. Wait for string. 582 */ 583 return (WaitforString(str)); 584 } 585 } 586 return (MATCH); 587} 588 589static jmp_buf ChatEnv; 590static void (*oint) (int); 591 592static void 593StopDial(int sig) 594{ 595 LogPrintf(LogPHASE, "DoChat: Caught signal %d, abort connect\n", sig); 596 longjmp(ChatEnv, 1); 597} 598 599int 600DoChat(char *script) 601{ 602 char *vector[40]; 603 char **argv; 604 int argc, n, state; 605 606 if (!script || !*script) 607 return MATCH; 608 609 /* While we're chatting, we want an INT to fail us */ 610 if (setjmp(ChatEnv)) { 611 signal(SIGINT, oint); 612 return (-1); 613 } 614 oint = signal(SIGINT, StopDial); 615 616 timeout_next = abort_next = 0; 617 for (n = 0; AbortStrings[n]; n++) { 618 free(AbortStrings[n]); 619 AbortStrings[n] = NULL; 620 } 621 numaborts = 0; 622 623 memset(vector, '\0', sizeof(vector)); 624 n = MakeArgs(script, vector, VECSIZE(vector)); 625 argc = n; 626 argv = vector; 627 TimeoutSec = 30; 628 while (*argv) { 629 if (strcmp(*argv, "P_ZERO") == 0 || 630 strcmp(*argv, "P_ODD") == 0 || strcmp(*argv, "P_EVEN") == 0) { 631 ChangeParity(*argv++); 632 continue; 633 } 634 state = ExpectString(*argv++); 635 switch (state) { 636 case MATCH: 637 if (*argv) 638 SendString(*argv++); 639 break; 640 case ABORT: 641 case NOMATCH: 642 signal(SIGINT, oint); 643 return (NOMATCH); 644 } 645 } 646 signal(SIGINT, oint); 647 return (MATCH); 648} 649