1 /*- 2 * Copyright (c) 1997 3 * David L Nugent <davidn@blaze.net.au>. 4 * All rights reserved. 5 * 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, is permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice immediately at the beginning of the file, without modification, 12 * this list of conditions, and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 3. This work was done expressly for inclusion into FreeBSD. Other use 17 * is permitted provided this notation is included. 18 * 4. Absolutely no warranty of function or purpose is made by the authors. 19 * 5. Modifications may be freely made to this file providing the above 20 * conditions are met. 21 * 22 * Modem chat module - send/expect style functions for getty 23 * For semi-intelligent modem handling. 24 */ 25 26 #ifndef lint 27 static const char rcsid[] = 28 "$Id$"; 29 #endif /* not lint */ 30 31 #include <sys/param.h> 32 #include <sys/stat.h> 33 #include <sys/ioctl.h> 34 #include <sys/resource.h> 35 #include <sys/ttydefaults.h> 36 #include <sys/utsname.h> 37 #include <ctype.h> 38 #include <errno.h> 39 #include <fcntl.h> 40 #include <libutil.h> 41 #include <locale.h> 42 #include <setjmp.h> 43 #include <signal.h> 44 #include <stdlib.h> 45 #include <string.h> 46 #include <syslog.h> 47 #include <time.h> 48 #include <termios.h> 49 #include <unistd.h> 50 #include <sys/socket.h> 51 52 #include "extern.h" 53 54 #define PAUSE_CH (unsigned char)'\xff' /* pause kludge */ 55 56 #define CHATDEBUG_RECEIVE 0x01 57 #define CHATDEBUG_SEND 0x02 58 #define CHATDEBUG_EXPECT 0x04 59 #define CHATDEBUG_MISC 0x08 60 61 #define CHATDEBUG_DEFAULT 0 62 #define CHAT_DEFAULT_TIMEOUT 10 63 64 65 static int chat_debug = CHATDEBUG_DEFAULT; 66 static int chat_alarm = CHAT_DEFAULT_TIMEOUT; /* Default */ 67 68 static volatile int alarmed = 0; 69 70 71 static void chat_alrm __P((int)); 72 static int chat_unalarm __P((void)); 73 static int getdigit __P((unsigned char **, int, int)); 74 static char **read_chat __P((char **)); 75 static char *cleanchr __P((char **, unsigned char)); 76 static char *cleanstr __P((const unsigned char *, int)); 77 static const char *result __P((int)); 78 static int chat_expect __P((const char *)); 79 static int chat_send __P((char const *)); 80 81 82 /* 83 * alarm signal handler 84 * handle timeouts in read/write 85 * change stdin to non-blocking mode to prevent 86 * possible hang in read(). 87 */ 88 89 static void 90 chat_alrm(signo) 91 int signo; 92 { 93 int on = 1; 94 95 alarm(1); 96 alarmed = 1; 97 signal(SIGALRM, chat_alrm); 98 ioctl(STDIN_FILENO, FIONBIO, &on); 99 } 100 101 102 /* 103 * Turn back on blocking mode reset by chat_alrm() 104 */ 105 106 static int 107 chat_unalarm() 108 { 109 int off = 0; 110 return ioctl(STDIN_FILENO, FIONBIO, &off); 111 } 112 113 114 /* 115 * convert a string of a given base (octal/hex) to binary 116 */ 117 118 static int 119 getdigit(ptr, base, max) 120 unsigned char **ptr; 121 int base, max; 122 { 123 int i, val = 0; 124 char * q; 125 126 static const char xdigits[] = "0123456789abcdef"; 127 128 for (i = 0, q = *ptr; i++ < max; ++q) { 129 int sval; 130 const char * s = strchr(xdigits, tolower(*q)); 131 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 147 static char ** 148 read_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 if (*q == '\\') 173 { 174 /* handle special escapes */ 175 switch (*++q) 176 { 177 case 'a': /* bell */ 178 *r++ = '\a'; 179 break; 180 case 'r': /* cr */ 181 *r++ = '\r'; 182 break; 183 case 'n': /* nl */ 184 *r++ = '\n'; 185 break; 186 case 'f': /* ff */ 187 *r++ = '\f'; 188 break; 189 case 'b': /* bs */ 190 *r++ = '\b'; 191 break; 192 case 'e': /* esc */ 193 *r++ = 27; 194 break; 195 case 't': /* tab */ 196 *r++ = '\t'; 197 break; 198 case 'p': /* pause */ 199 *r++ = PAUSE_CH; 200 break; 201 case 's': 202 case 'S': /* space */ 203 *r++ = ' '; 204 break; 205 case 'x': /* hexdigit */ 206 ++q; 207 *r++ = getdigit(&q, 16, 2); 208 --q; 209 break; 210 case '0': /* octal */ 211 ++q; 212 *r++ = getdigit(&q, 8, 3); 213 --q; 214 break; 215 default: /* literal */ 216 *r++ = *q; 217 break; 218 case 0: /* not past eos */ 219 --q; 220 break; 221 } 222 } else { 223 /* copy standard character */ 224 *r++ = *q; 225 } 226 } 227 228 /* Remove surrounding quotes, if any 229 */ 230 if (*p == '"' || *p == '\'') { 231 q = strrchr(p+1, *p); 232 if (q != NULL && *q == *p && q[1] == '\0') { 233 *q = '\0'; 234 strcpy(p, p+1); 235 } 236 } 237 238 res[l++] = p; 239 } 240 res[l] = NULL; 241 *chatstr = tmp; 242 return res; 243 } 244 free(tmp); 245 } 246 return res; 247 } 248 249 250 /* 251 * clean a character for display (ctrl/meta character) 252 */ 253 254 static char * 255 cleanchr(buf, ch) 256 char **buf; 257 unsigned char ch; 258 { 259 int l; 260 static char tmpbuf[5]; 261 char * tmp = buf ? *buf : tmpbuf; 262 263 if (ch & 0x80) { 264 strcpy(tmp, "M-"); 265 l = 2; 266 ch &= 0x7f; 267 } else 268 l = 0; 269 270 if (ch < 32) { 271 tmp[l++] = '^'; 272 tmp[l++] = ch + '@'; 273 } else if (ch == 127) { 274 tmp[l++] = '^'; 275 tmp[l++] = '?'; 276 } else 277 tmp[l++] = ch; 278 tmp[l] = '\0'; 279 280 if (buf) 281 *buf = tmp + l; 282 return tmp; 283 } 284 285 286 /* 287 * clean a string for display (ctrl/meta characters) 288 */ 289 290 static char * 291 cleanstr(s, l) 292 const unsigned char *s; 293 int l; 294 { 295 static unsigned char * tmp = NULL; 296 static int tmplen = 0; 297 298 if (tmplen < l * 4 + 1) 299 tmp = realloc(tmp, tmplen = l * 4 + 1); 300 301 if (tmp == NULL) { 302 tmplen = 0; 303 return (char *)"(mem alloc error)"; 304 } else { 305 int i = 0; 306 char * p = tmp; 307 308 while (i < l) 309 cleanchr(&p, s[i++]); 310 *p = '\0'; 311 } 312 313 return tmp; 314 } 315 316 317 /* 318 * return result as an pseudo-english word 319 */ 320 321 static const char * 322 result(r) 323 int r; 324 { 325 static const char * results[] = { 326 "OK", "MEMERROR", "IOERROR", "TIMEOUT" 327 }; 328 return results[r & 3]; 329 } 330 331 332 /* 333 * chat_expect() 334 * scan input for an expected string 335 */ 336 337 static int 338 chat_expect(str) 339 const char *str; 340 { 341 int len, r = 0; 342 343 if (chat_debug & CHATDEBUG_EXPECT) 344 syslog(LOG_DEBUG, "chat_expect '%s'", cleanstr(str, strlen(str))); 345 346 if ((len = strlen(str)) > 0) { 347 int i = 0; 348 char * got; 349 350 if ((got = malloc(len + 1)) == NULL) 351 r = 1; 352 else { 353 354 memset(got, 0, len+1); 355 alarm(chat_alarm); 356 alarmed = 0; 357 358 while (r == 0 && i < len) { 359 if (alarmed) 360 r = 3; 361 else { 362 unsigned char ch; 363 364 if (read(STDIN_FILENO, &ch, 1) == 1) { 365 366 if (chat_debug & CHATDEBUG_RECEIVE) 367 syslog(LOG_DEBUG, "chat_recv '%s' m=%d", 368 cleanchr(NULL, ch), i); 369 370 if (ch == str[i]) 371 got[i++] = ch; 372 else if (i > 0) { 373 int j = 1; 374 375 /* See if we can resync on a 376 * partial match in our buffer 377 */ 378 while (j < i && memcmp(got + j, str, i - j) != NULL) 379 j++; 380 if (j < i) 381 memcpy(got, got + j, i - j); 382 i -= j; 383 } 384 } else 385 r = alarmed ? 3 : 2; 386 } 387 } 388 alarm(0); 389 chat_unalarm(); 390 alarmed = 0; 391 free(got); 392 } 393 } 394 395 if (chat_debug & CHATDEBUG_EXPECT) 396 syslog(LOG_DEBUG, "chat_expect %s", result(r)); 397 398 return r; 399 } 400 401 402 /* 403 * chat_send() 404 * send a chat string 405 */ 406 407 static int 408 chat_send(str) 409 char const *str; 410 { 411 int r = 0; 412 413 if (chat_debug && CHATDEBUG_SEND) 414 syslog(LOG_DEBUG, "chat_send '%s'", cleanstr(str, strlen(str))); 415 416 if (*str) { 417 alarm(chat_alarm); 418 alarmed = 0; 419 while (r == 0 && *str) 420 { 421 unsigned char ch = (unsigned char)*str++; 422 423 if (alarmed) 424 r = 3; 425 else if (ch == PAUSE_CH) 426 usleep(500000); /* 1/2 second */ 427 else { 428 usleep(10000); /* be kind to modem */ 429 if (write(STDOUT_FILENO, &ch, 1) != 1) 430 r = alarmed ? 3 : 2; 431 } 432 } 433 alarm(0); 434 chat_unalarm(); 435 alarmed = 0; 436 } 437 438 if (chat_debug & CHATDEBUG_SEND) 439 syslog(LOG_DEBUG, "chat_send %s", result(r)); 440 441 return r; 442 } 443 444 445 /* 446 * getty_chat() 447 * 448 * Termination codes: 449 * -1 - no script supplied 450 * 0 - script terminated correctly 451 * 1 - invalid argument, expect string too large, etc. 452 * 2 - error on an I/O operation or fatal error condition 453 * 3 - timeout waiting for a simple string 454 * 455 * Parameters: 456 * char *scrstr - unparsed chat script 457 * timeout - seconds timeout 458 * debug - debug value (bitmask) 459 */ 460 461 int 462 getty_chat(scrstr, timeout, debug) 463 char *scrstr; 464 int timeout, debug; 465 { 466 int r = -1; 467 468 chat_alarm = timeout ? timeout : CHAT_DEFAULT_TIMEOUT; 469 chat_debug = debug; 470 471 if (scrstr != NULL) { 472 char **script; 473 474 if (chat_debug & CHATDEBUG_MISC) 475 syslog(LOG_DEBUG, "getty_chat script='%s'", scrstr); 476 477 if ((script = read_chat(&scrstr)) != NULL) { 478 int i = r = 0; 479 int off = 0; 480 sig_t old_alarm; 481 482 /* 483 * We need to be in raw mode for all this 484 * Rely on caller... 485 */ 486 487 old_alarm = signal(SIGALRM, chat_alrm); 488 chat_unalarm(); /* Force blocking mode at start */ 489 490 /* 491 * This is the send/expect loop 492 */ 493 while (r == 0 && script[i] != NULL) 494 if ((r = chat_expect(script[i++])) == 0 && script[i] != NULL) 495 r = chat_send(script[i++]); 496 497 signal(SIGALRM, old_alarm); 498 free(script); 499 free(scrstr); 500 501 /* 502 * Ensure stdin is in blocking mode 503 */ 504 ioctl(STDIN_FILENO, FIONBIO, &off); 505 } 506 507 if (chat_debug & CHATDEBUG_MISC) 508 syslog(LOG_DEBUG, "getty_chat %s", result(r)); 509 510 } 511 return r; 512 } 513