chat.c revision 22989
11556Srgrimes/*- 21556Srgrimes * Copyright (c) 1997 31556Srgrimes * David L Nugent <davidn@blaze.net.au>. 41556Srgrimes * All rights reserved. 51556Srgrimes * 61556Srgrimes * 71556Srgrimes * Redistribution and use in source and binary forms, with or without 81556Srgrimes * modification, is permitted provided that the following conditions 91556Srgrimes * are met: 101556Srgrimes * 1. Redistributions of source code must retain the above copyright 111556Srgrimes * notice immediately at the beginning of the file, without modification, 121556Srgrimes * this list of conditions, and the following disclaimer. 131556Srgrimes * 2. Redistributions in binary form must reproduce the above copyright 141556Srgrimes * notice, this list of conditions and the following disclaimer in the 151556Srgrimes * documentation and/or other materials provided with the distribution. 161556Srgrimes * 3. This work was done expressly for inclusion into FreeBSD. Other use 171556Srgrimes * is permitted provided this notation is included. 181556Srgrimes * 4. Absolutely no warranty of function or purpose is made by the authors. 191556Srgrimes * 5. Modifications may be freely made to this file providing the above 201556Srgrimes * conditions are met. 211556Srgrimes * 221556Srgrimes * Modem chat module - send/expect style functions for getty 231556Srgrimes * For semi-intelligent modem handling. 241556Srgrimes * 251556Srgrimes * $Id$ 261556Srgrimes */ 271556Srgrimes 281556Srgrimes#include <sys/param.h> 291556Srgrimes#include <sys/stat.h> 301556Srgrimes#include <sys/ioctl.h> 311556Srgrimes#include <sys/resource.h> 3217987Speter#include <sys/ttydefaults.h> 3350471Speter#include <sys/utsname.h> 341556Srgrimes#include <errno.h> 351556Srgrimes#include <signal.h> 361556Srgrimes#include <fcntl.h> 371556Srgrimes#include <time.h> 381556Srgrimes#include <ctype.h> 391556Srgrimes#include <fcntl.h> 401556Srgrimes#include <libutil.h> 4120425Ssteve#include <locale.h> 4220425Ssteve#include <setjmp.h> 43155303Sschweikh#include <signal.h> 44155303Sschweikh#include <stdlib.h> 4520425Ssteve#include <string.h> 4620425Ssteve#include <syslog.h> 4720425Ssteve#include <termios.h> 48216870Sjilles#include <time.h> 49223024Sjilles#include <unistd.h> 501556Srgrimes#include <sys/socket.h> 511556Srgrimes 521556Srgrimes#include "extern.h" 531556Srgrimes 5420425Ssteve#define PAUSE_CH (unsigned char)'\xff' /* pause kludge */ 55221668Sjilles 5620425Ssteve#define CHATDEBUG_RECEIVE 0x01 5790111Simp#define CHATDEBUG_SEND 0x02 5820425Ssteve#define CHATDEBUG_EXPECT 0x04 5920425Ssteve#define CHATDEBUG_MISC 0x08 601556Srgrimes 611556Srgrimes#define CHATDEBUG_DEFAULT 0 621556Srgrimes#define CHAT_DEFAULT_TIMEOUT 10 631556Srgrimes 6420425Ssteve 6520425Sstevestatic int chat_debug = CHATDEBUG_DEFAULT; 6620425Sstevestatic int chat_alarm = CHAT_DEFAULT_TIMEOUT; /* Default */ 6720425Ssteve 681556Srgrimesstatic volatile int alarmed = 0; 691556Srgrimes 701556Srgrimes 711556Srgrimesstatic void chat_alrm __P((int)); 72223024Sjillesstatic int chat_unalarm __P((void)); 731556Srgrimesstatic int getdigit __P((unsigned char **, int, int)); 741556Srgrimesstatic char **read_chat __P((char **)); 751556Srgrimesstatic char *cleanchr __P((char **, unsigned char)); 761556Srgrimesstatic char *cleanstr __P((const unsigned char *, int)); 771556Srgrimesstatic const char *result __P((int)); 7897689Stjrstatic int chat_expect __P((const char *)); 791556Srgrimesstatic int chat_send __P((char const *)); 801556Srgrimes 81159632Sstefanf 82230998Sjilles/* 8320425Ssteve * alarm signal handler 8420425Ssteve * handle timeouts in read/write 85208755Sjilles * change stdin to non-blocking mode to prevent 8620425Ssteve * possible hang in read(). 871556Srgrimes */ 88221559Sjilles 89221669Sjillesstatic void 90221669Sjilleschat_alrm(signo) 91221559Sjilles int signo; 921556Srgrimes{ 931556Srgrimes int on = 1; 941556Srgrimes 951556Srgrimes alarm(1); 961556Srgrimes alarmed = 1; 971556Srgrimes signal(SIGALRM, chat_alrm); 981556Srgrimes ioctl(STDIN_FILENO, FIONBIO, &on); 9938887Stegge} 1001556Srgrimes 1011556Srgrimes 1021556Srgrimes/* 1031556Srgrimes * Turn back on blocking mode reset by chat_alrm() 1041556Srgrimes */ 105159632Sstefanf 10620425Sstevestatic int 10720425Sstevechat_unalarm() 10820425Ssteve{ 109208755Sjilles int off = 0; 11020425Ssteve return ioctl(STDIN_FILENO, FIONBIO, &off); 1111556Srgrimes} 1121556Srgrimes 113230998Sjilles 1141556Srgrimes/* 11590111Simp * convert a string of a given base (octal/hex) to binary 116200956Sjilles */ 11790111Simp 1181556Srgrimesstatic int 119216870Sjillesgetdigit(ptr, base, max) 120200956Sjilles unsigned char **ptr; 121200956Sjilles int base, max; 122207678Sjilles{ 123207678Sjilles int i, val = 0; 124221559Sjilles char * q; 125221669Sjilles 12690111Simp static const char xdigits[] = "0123456789abcdef"; 12790111Simp 12890111Simp for (i = 0, q = *ptr; i++ < max; ++q) { 12990111Simp int sval; 130200956Sjilles const char * s = strchr(xdigits, tolower(*q)); 131200956Sjilles 132 if (s == NULL || (sval = s - xdigits) >= base) 133 break; 134 val = (val * base) + sval; 135 } 136 *ptr = q; 137 return val; 138} 139 140 141/* 142 * read_chat() 143 * Convert a whitespace delimtied string into an array 144 * of strings, being expect/send pairs 145 */ 146 147static char ** 148read_chat(chatstr) 149 char **chatstr; 150{ 151 char *str = *chatstr; 152 char **res = NULL; 153 154 if (str != NULL) { 155 char *tmp = NULL; 156 int l; 157 158 if ((l=strlen(str)) > 0 && (tmp=malloc(l + 1)) != NULL && 159 (res=malloc((l / 2 + 1) * sizeof(char *))) != NULL) { 160 static char ws[] = " \t"; 161 char * p; 162 163 for (l = 0, p = strtok(strcpy(tmp, str), ws); 164 p != NULL; 165 p = strtok(NULL, ws)) 166 { 167 unsigned char *q, *r; 168 169 /* Read escapes */ 170 for (q = r = (unsigned char *)p; *r; ++q) 171 { 172 int val; 173 174 if (*q == '\\') 175 { 176 /* handle special escapes */ 177 switch (*++q) 178 { 179 case 'a': /* bell */ 180 *r++ = '\a'; 181 break; 182 case 'r': /* cr */ 183 *r++ = '\r'; 184 break; 185 case 'n': /* nl */ 186 *r++ = '\n'; 187 break; 188 case 'f': /* ff */ 189 *r++ = '\f'; 190 break; 191 case 'b': /* bs */ 192 *r++ = '\b'; 193 break; 194 case 'e': /* esc */ 195 *r++ = 27; 196 break; 197 case 't': /* tab */ 198 *r++ = '\t'; 199 break; 200 case 'p': /* pause */ 201 *r++ = PAUSE_CH; 202 break; 203 case 's': 204 case 'S': /* space */ 205 *r++ = ' '; 206 break; 207 case 'x': /* hexdigit */ 208 ++q; 209 *r++ = getdigit(&q, 16, 2); 210 --q; 211 break; 212 case '0': /* octal */ 213 ++q; 214 *r++ = getdigit(&q, 8, 3); 215 --q; 216 break; 217 default: /* literal */ 218 *r++ = *q; 219 break; 220 case 0: /* not past eos */ 221 --q; 222 break; 223 } 224 } else { 225 /* copy standard character */ 226 *r++ == *q; 227 } 228 } 229 230 /* Remove surrounding quotes, if any 231 */ 232 if (*p == '"' || *p == '\'') { 233 q = strrchr(p+1, *p); 234 if (q != NULL && *q == *p && q[1] == '\0') { 235 *q = '\0'; 236 strcpy(p, p+1); 237 } 238 } 239 240 res[l++] = p; 241 } 242 res[l] = NULL; 243 *chatstr = tmp; 244 return res; 245 } 246 free(tmp); 247 } 248 return res; 249} 250 251 252/* 253 * clean a character for display (ctrl/meta character) 254 */ 255 256static char * 257cleanchr(buf, ch) 258 char **buf; 259 unsigned char ch; 260{ 261 int l; 262 static char tmpbuf[5]; 263 char * tmp = buf ? *buf : tmpbuf; 264 265 if (ch & 0x80) { 266 strcpy(tmp, "M-"); 267 l = 2; 268 ch &= 0x7f; 269 } else 270 l = 0; 271 272 if (ch < 32) { 273 tmp[l++] = '^'; 274 tmp[l++] = ch + '@'; 275 } else if (ch == 127) { 276 tmp[l++] = '^'; 277 tmp[l++] = '?'; 278 } else 279 tmp[l++] = ch; 280 tmp[l] = '\0'; 281 282 if (buf) 283 *buf = tmp + l; 284 return tmp; 285} 286 287 288/* 289 * clean a string for display (ctrl/meta characters) 290 */ 291 292static char * 293cleanstr(s, l) 294 const unsigned char *s; 295 int l; 296{ 297 static unsigned char * tmp = NULL; 298 static int tmplen = 0; 299 300 if (tmplen < l * 4 + 1) 301 tmp = realloc(tmp, tmplen = l * 4 + 1); 302 303 if (tmp == NULL) { 304 tmplen = 0; 305 return (char *)"(mem alloc error)"; 306 } else { 307 int i = 0; 308 char * p = tmp; 309 310 while (i < l) 311 cleanchr(&p, s[i++]); 312 *p = '\0'; 313 } 314 315 return tmp; 316} 317 318 319/* 320 * return result as an pseudo-english word 321 */ 322 323static const char * 324result(r) 325 int r; 326{ 327 static const char * results[] = { 328 "OK", "MEMERROR", "IOERROR", "TIMEOUT" 329 }; 330 return results[r & 3]; 331} 332 333 334/* 335 * chat_expect() 336 * scan input for an expected string 337 */ 338 339static int 340chat_expect(str) 341 const char *str; 342{ 343 int len, r = 0; 344 345 if (chat_debug & CHATDEBUG_EXPECT) 346 syslog(LOG_DEBUG, "chat_expect '%s'", cleanstr(str, strlen(str))); 347 348 if ((len = strlen(str)) > 0) { 349 int i = 0; 350 char * got; 351 352 if ((got = malloc(len + 1)) == NULL) 353 r = 1; 354 else { 355 356 memset(got, 0, len+1); 357 alarm(chat_alarm); 358 alarmed = 0; 359 360 while (r == 0 && i < len) { 361 if (alarmed) 362 r = 3; 363 else { 364 unsigned char ch; 365 366 if (read(STDIN_FILENO, &ch, 1) == 1) { 367 368 if (chat_debug & CHATDEBUG_RECEIVE) 369 syslog(LOG_DEBUG, "chat_recv '%s' m=%d", 370 cleanchr(NULL, ch), i); 371 372 if (ch == str[i]) 373 got[i++] = ch; 374 else if (i > 0) { 375 int j = 1; 376 377 /* See if we can resync on a 378 * partial match in our buffer 379 */ 380 while (j < i && memcmp(got + j, str, i - j) != NULL) 381 j++; 382 if (j < i) 383 memcpy(got, got + j, i - j); 384 i -= j; 385 } 386 } else 387 r = alarmed ? 3 : 2; 388 } 389 } 390 alarm(0); 391 chat_unalarm(); 392 alarmed = 0; 393 free(got); 394 } 395 } 396 397 if (chat_debug & CHATDEBUG_EXPECT) 398 syslog(LOG_DEBUG, "chat_expect %s", result(r)); 399 400 return r; 401} 402 403 404/* 405 * chat_send() 406 * send a chat string 407 */ 408 409static int 410chat_send(str) 411 char const *str; 412{ 413 int r = 0; 414 415 if (chat_debug && CHATDEBUG_SEND) 416 syslog(LOG_DEBUG, "chat_send '%s'", cleanstr(str, strlen(str))); 417 418 if (*str) { 419 alarm(chat_alarm); 420 alarmed = 0; 421 while (r == 0 && *str) 422 { 423 unsigned char ch = (unsigned char)*str++; 424 425 if (alarmed) 426 r = 3; 427 else if (ch == PAUSE_CH) 428 usleep(500000); /* 1/2 second */ 429 else { 430 usleep(10000); /* be kind to modem */ 431 if (write(STDOUT_FILENO, &ch, 1) != 1) 432 r = alarmed ? 3 : 2; 433 } 434 } 435 alarm(0); 436 chat_unalarm(); 437 alarmed = 0; 438 } 439 440 if (chat_debug & CHATDEBUG_SEND) 441 syslog(LOG_DEBUG, "chat_send %s", result(r)); 442 443 return r; 444} 445 446 447/* 448 * getty_chat() 449 * 450 * Termination codes: 451 * -1 - no script supplied 452 * 0 - script terminated correctly 453 * 1 - invalid argument, expect string too large, etc. 454 * 2 - error on an I/O operation or fatal error condition 455 * 3 - timeout waiting for a simple string 456 * 457 * Parameters: 458 * char *scrstr - unparsed chat script 459 * timeout - seconds timeout 460 * debug - debug value (bitmask) 461 */ 462 463int 464getty_chat(scrstr, timeout, debug) 465 char *scrstr; 466 int timeout, debug; 467{ 468 int r = -1; 469 470 chat_alarm = timeout ? timeout : CHAT_DEFAULT_TIMEOUT; 471 chat_debug = debug; 472 473 if (scrstr != NULL) { 474 char **script; 475 476 if (chat_debug & CHATDEBUG_MISC) 477 syslog(LOG_DEBUG, "getty_chat script='%s'", scrstr); 478 479 if ((script = read_chat(&scrstr)) != NULL) { 480 int i = r = 0; 481 int off = 0; 482 sig_t old_alarm; 483 struct termios tneed; 484 485 /* 486 * We need to be in raw mode for all this 487 * Rely on caller... 488 */ 489 490 old_alarm = signal(SIGALRM, chat_alrm); 491 chat_unalarm(); /* Force blocking mode at start */ 492 493 /* 494 * This is the send/expect loop 495 */ 496 while (r == 0 && script[i] != NULL) 497 if ((r = chat_expect(script[i++])) == 0 && script[i] != NULL) 498 r = chat_send(script[i++]); 499 500 signal(SIGALRM, old_alarm); 501 free(script); 502 free(scrstr); 503 504 /* 505 * Ensure stdin is in blocking mode 506 */ 507 ioctl(STDIN_FILENO, FIONBIO, &off); 508 } 509 510 if (chat_debug & CHATDEBUG_MISC) 511 syslog(LOG_DEBUG, "getty_chat %s", result(r)); 512 513 } 514 return r; 515} 516