chat.c revision 31830
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.39 1997/11/22 03:37:26 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 "command.h" 47#include "mbuf.h" 48#include "log.h" 49#include "defs.h" 50#include "timer.h" 51#include "loadalias.h" 52#include "vars.h" 53#include "chat.h" 54#include "sig.h" 55#include "modem.h" 56 57#ifndef isblank 58#define isblank(c) ((c) == '\t' || (c) == ' ') 59#endif 60 61 62#define IBSIZE LINE_LEN 63 64static int TimeoutSec; 65static int abort_next, timeout_next; 66static int numaborts; 67static char *AbortStrings[50]; 68static char inbuff[IBSIZE * 2 + 1]; 69 70#define MATCH 1 71#define NOMATCH 0 72#define ABORT -1 73 74static char * 75findblank(char *p, int instring) 76{ 77 if (instring) { 78 while (*p) { 79 if (*p == '\\') { 80 strcpy(p, p + 1); 81 if (!*p) 82 break; 83 } else if (*p == '"') 84 return (p); 85 p++; 86 } 87 } else { 88 while (*p) { 89 if (isblank(*p)) 90 return (p); 91 p++; 92 } 93 } 94 return p; 95} 96 97int 98MakeArgs(char *script, char **pvect, int maxargs) 99{ 100 int nargs, nb; 101 int instring; 102 103 nargs = 0; 104 while (*script) { 105 nb = strspn(script, " \t"); 106 script += nb; 107 if (*script) { 108 if (*script == '"') { 109 instring = 1; 110 script++; 111 if (*script == '\0') 112 break; /* Shouldn't return here. Need to null 113 * terminate below */ 114 } else 115 instring = 0; 116 if (nargs >= maxargs - 1) 117 break; 118 *pvect++ = script; 119 nargs++; 120 script = findblank(script, instring); 121 if (*script) 122 *script++ = '\0'; 123 } 124 } 125 *pvect = NULL; 126 return nargs; 127} 128 129/* 130 * \c don't add a cr 131 * \d Sleep a little (delay 2 seconds 132 * \n Line feed character 133 * \P Auth Key password 134 * \p pause 0.25 sec 135 * \r Carrige return character 136 * \s Space character 137 * \T Telephone number(s) (defined via `set phone') 138 * \t Tab character 139 * \U Auth User 140 */ 141char * 142ExpandString(const char *str, char *result, int reslen, int sendmode) 143{ 144 int addcr = 0; 145 char *phone; 146 147 result[--reslen] = '\0'; 148 if (sendmode) 149 addcr = 1; 150 while (*str && reslen > 0) { 151 switch (*str) { 152 case '\\': 153 str++; 154 switch (*str) { 155 case 'c': 156 if (sendmode) 157 addcr = 0; 158 break; 159 case 'd': /* Delay 2 seconds */ 160 nointr_sleep(2); 161 break; 162 case 'p': 163 nointr_usleep(250000); 164 break; /* Pause 0.25 sec */ 165 case 'n': 166 *result++ = '\n'; 167 reslen--; 168 break; 169 case 'r': 170 *result++ = '\r'; 171 reslen--; 172 break; 173 case 's': 174 *result++ = ' '; 175 reslen--; 176 break; 177 case 't': 178 *result++ = '\t'; 179 reslen--; 180 break; 181 case 'P': 182 strncpy(result, VarAuthKey, reslen); 183 reslen -= strlen(result); 184 result += strlen(result); 185 break; 186 case 'T': 187 if (VarAltPhone == NULL) { 188 if (VarNextPhone == NULL) { 189 strncpy(VarPhoneCopy, VarPhoneList, sizeof(VarPhoneCopy)); 190 VarPhoneCopy[sizeof(VarPhoneCopy) - 1] = '\0'; 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(void) 244{ 245 memset(logbuff, 0, MAXLOGBUFF); 246 loglen = 0; 247} 248 249static void 250flush_log(void) 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(const 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(const 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 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[MAXARGS]; 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 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, (char **)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(const 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 ExpandString(str + 1, buff + 2, sizeof(buff) - 2, 0); 502 ExecStr(buff + 2, buff + 2); 503 } else { 504 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'; /* XXX: Cheat with the const string */ 548 state = WaitforString(str); 549 *minus = '-'; /* XXX: Cheat with the const string */ 550 minus++; 551 if (state != NOMATCH) 552 return (state); 553 554 /* 555 * Can't get expect string. Sendout send part. 556 */ 557 str = minus; 558 for (minus = str; *minus; minus++) { 559 if (*minus == '-') { 560 if (minus == str || minus[-1] != '\\') 561 break; 562 } 563 } 564 if (*minus == '-') { 565 *minus = '\0'; /* XXX: Cheat with the const string */ 566 SendString(str); 567 *minus = '-'; /* XXX: Cheat with the const string */ 568 str = ++minus; 569 } else { 570 SendString(str); 571 return (MATCH); 572 } 573 } else { 574 575 /* 576 * Simple case. Wait for string. 577 */ 578 return (WaitforString(str)); 579 } 580 } 581 return (MATCH); 582} 583 584static jmp_buf ChatEnv; 585static void (*oint) (int); 586 587static void 588StopDial(int sig) 589{ 590 LogPrintf(LogPHASE, "DoChat: Caught signal %d, abort connect\n", sig); 591 longjmp(ChatEnv, 1); 592} 593 594int 595DoChat(char *script) 596{ 597 char *vector[MAXARGS]; 598 char *const *argv; 599 int argc, n, state; 600 601 if (!script || !*script) 602 return MATCH; 603 604 /* While we're chatting, we want an INT to fail us */ 605 if (setjmp(ChatEnv)) { 606 signal(SIGINT, oint); 607 return (-1); 608 } 609 oint = signal(SIGINT, StopDial); 610 611 timeout_next = abort_next = 0; 612 for (n = 0; AbortStrings[n]; n++) { 613 free(AbortStrings[n]); 614 AbortStrings[n] = NULL; 615 } 616 numaborts = 0; 617 618 memset(vector, '\0', sizeof(vector)); 619 argc = MakeArgs(script, vector, VECSIZE(vector)); 620 argv = vector; 621 TimeoutSec = 30; 622 while (*argv) { 623 if (strcmp(*argv, "P_ZERO") == 0 || 624 strcmp(*argv, "P_ODD") == 0 || strcmp(*argv, "P_EVEN") == 0) { 625 ChangeParity(*argv++); 626 continue; 627 } 628 state = ExpectString(*argv++); 629 switch (state) { 630 case MATCH: 631 if (*argv) 632 SendString(*argv++); 633 break; 634 case ABORT: 635 case NOMATCH: 636 signal(SIGINT, oint); 637 return (NOMATCH); 638 } 639 } 640 signal(SIGINT, oint); 641 return (MATCH); 642} 643