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 "$FreeBSD$"; 29 #endif /* not lint */ 30 31 #include <sys/types.h> 32 #include <sys/ioctl.h> 33 #include <sys/utsname.h> 34 35 #include <ctype.h> 36 #include <signal.h> 37 #include <stdlib.h> 38 #include <string.h> 39 #include <syslog.h> 40 #include <unistd.h> 41 42 #include "extern.h" 43 44 #define PAUSE_CH (unsigned char)'\xff' /* pause kludge */ 45 46 #define CHATDEBUG_RECEIVE 0x01 47 #define CHATDEBUG_SEND 0x02 48 #define CHATDEBUG_EXPECT 0x04 49 #define CHATDEBUG_MISC 0x08 50 51 #define CHATDEBUG_DEFAULT 0 52 #define CHAT_DEFAULT_TIMEOUT 10 53 54 55 static int chat_debug = CHATDEBUG_DEFAULT; 56 static int chat_alarm = CHAT_DEFAULT_TIMEOUT; /* Default */ 57 58 static volatile int alarmed = 0; 59 60 61 static void chat_alrm(int); 62 static int chat_unalarm(void); 63 static int getdigit(unsigned char **, int, int); 64 static char **read_chat(char **); 65 static char *cleanchr(char **, unsigned char); 66 static char *cleanstr(const unsigned char *, int); 67 static const char *result(int); 68 static int chat_expect(const char *); 69 static int chat_send(char const *); 70 71 72 /* 73 * alarm signal handler 74 * handle timeouts in read/write 75 * change stdin to non-blocking mode to prevent 76 * possible hang in read(). 77 */ 78 79 static void 80 chat_alrm(int signo) 81 { 82 int on = 1; 83 84 alarm(1); 85 alarmed = 1; 86 signal(SIGALRM, chat_alrm); 87 ioctl(STDIN_FILENO, FIONBIO, &on); 88 } 89 90 91 /* 92 * Turn back on blocking mode reset by chat_alrm() 93 */ 94 95 static int 96 chat_unalarm(void) 97 { 98 int off = 0; 99 return ioctl(STDIN_FILENO, FIONBIO, &off); 100 } 101 102 103 /* 104 * convert a string of a given base (octal/hex) to binary 105 */ 106 107 static int 108 getdigit(unsigned char **ptr, int base, int max) 109 { 110 int i, val = 0; 111 char * q; 112 113 static const char xdigits[] = "0123456789abcdef"; 114 115 for (i = 0, q = *ptr; i++ < max; ++q) { 116 int sval; 117 const char * s = strchr(xdigits, tolower(*q)); 118 119 if (s == NULL || (sval = s - xdigits) >= base) 120 break; 121 val = (val * base) + sval; 122 } 123 *ptr = q; 124 return val; 125 } 126 127 128 /* 129 * read_chat() 130 * Convert a whitespace delimtied string into an array 131 * of strings, being expect/send pairs 132 */ 133 134 static char ** 135 read_chat(char **chatstr) 136 { 137 char *str = *chatstr; 138 char **res = NULL; 139 140 if (str != NULL) { 141 char *tmp = NULL; 142 int l; 143 144 if ((l=strlen(str)) > 0 && (tmp=malloc(l + 1)) != NULL && 145 (res=malloc((l / 2 + 1) * sizeof(char *))) != NULL) { 146 static char ws[] = " \t"; 147 char * p; 148 149 for (l = 0, p = strtok(strcpy(tmp, str), ws); 150 p != NULL; 151 p = strtok(NULL, ws)) 152 { 153 unsigned char *q, *r; 154 155 /* Read escapes */ 156 for (q = r = (unsigned char *)p; *r; ++q) 157 { 158 if (*q == '\\') 159 { 160 /* handle special escapes */ 161 switch (*++q) 162 { 163 case 'a': /* bell */ 164 *r++ = '\a'; 165 break; 166 case 'r': /* cr */ 167 *r++ = '\r'; 168 break; 169 case 'n': /* nl */ 170 *r++ = '\n'; 171 break; 172 case 'f': /* ff */ 173 *r++ = '\f'; 174 break; 175 case 'b': /* bs */ 176 *r++ = '\b'; 177 break; 178 case 'e': /* esc */ 179 *r++ = 27; 180 break; 181 case 't': /* tab */ 182 *r++ = '\t'; 183 break; 184 case 'p': /* pause */ 185 *r++ = PAUSE_CH; 186 break; 187 case 's': 188 case 'S': /* space */ 189 *r++ = ' '; 190 break; 191 case 'x': /* hexdigit */ 192 ++q; 193 *r++ = getdigit(&q, 16, 2); 194 --q; 195 break; 196 case '0': /* octal */ 197 ++q; 198 *r++ = getdigit(&q, 8, 3); 199 --q; 200 break; 201 default: /* literal */ 202 *r++ = *q; 203 break; 204 case 0: /* not past eos */ 205 --q; 206 break; 207 } 208 } else { 209 /* copy standard character */ 210 *r++ = *q; 211 } 212 } 213 214 /* Remove surrounding quotes, if any 215 */ 216 if (*p == '"' || *p == '\'') { 217 q = strrchr(p+1, *p); 218 if (q != NULL && *q == *p && q[1] == '\0') { 219 *q = '\0'; 220 strcpy(p, p+1); 221 } 222 } 223 224 res[l++] = p; 225 } 226 res[l] = NULL; 227 *chatstr = tmp; 228 return res; 229 } 230 free(tmp); 231 } 232 return res; 233 } 234 235 236 /* 237 * clean a character for display (ctrl/meta character) 238 */ 239 240 static char * 241 cleanchr(char **buf, unsigned char ch) 242 { 243 int l; 244 static char tmpbuf[5]; 245 char * tmp = buf ? *buf : tmpbuf; 246 247 if (ch & 0x80) { 248 strcpy(tmp, "M-"); 249 l = 2; 250 ch &= 0x7f; 251 } else 252 l = 0; 253 254 if (ch < 32) { 255 tmp[l++] = '^'; 256 tmp[l++] = ch + '@'; 257 } else if (ch == 127) { 258 tmp[l++] = '^'; 259 tmp[l++] = '?'; 260 } else 261 tmp[l++] = ch; 262 tmp[l] = '\0'; 263 264 if (buf) 265 *buf = tmp + l; 266 return tmp; 267 } 268 269 270 /* 271 * clean a string for display (ctrl/meta characters) 272 */ 273 274 static char * 275 cleanstr(const unsigned char *s, int l) 276 { 277 static unsigned char * tmp = NULL; 278 static int tmplen = 0; 279 280 if (tmplen < l * 4 + 1) 281 tmp = realloc(tmp, tmplen = l * 4 + 1); 282 283 if (tmp == NULL) { 284 tmplen = 0; 285 return (char *)"(mem alloc error)"; 286 } else { 287 int i = 0; 288 char * p = tmp; 289 290 while (i < l) 291 cleanchr(&p, s[i++]); 292 *p = '\0'; 293 } 294 295 return tmp; 296 } 297 298 299 /* 300 * return result as a pseudo-english word 301 */ 302 303 static const char * 304 result(int r) 305 { 306 static const char * results[] = { 307 "OK", "MEMERROR", "IOERROR", "TIMEOUT" 308 }; 309 return results[r & 3]; 310 } 311 312 313 /* 314 * chat_expect() 315 * scan input for an expected string 316 */ 317 318 static int 319 chat_expect(const char *str) 320 { 321 int len, r = 0; 322 323 if (chat_debug & CHATDEBUG_EXPECT) 324 syslog(LOG_DEBUG, "chat_expect '%s'", cleanstr(str, strlen(str))); 325 326 if ((len = strlen(str)) > 0) { 327 int i = 0; 328 char * got; 329 330 if ((got = malloc(len + 1)) == NULL) 331 r = 1; 332 else { 333 334 memset(got, 0, len+1); 335 alarm(chat_alarm); 336 alarmed = 0; 337 338 while (r == 0 && i < len) { 339 if (alarmed) 340 r = 3; 341 else { 342 unsigned char ch; 343 344 if (read(STDIN_FILENO, &ch, 1) == 1) { 345 346 if (chat_debug & CHATDEBUG_RECEIVE) 347 syslog(LOG_DEBUG, "chat_recv '%s' m=%d", 348 cleanchr(NULL, ch), i); 349 350 if (ch == str[i]) 351 got[i++] = ch; 352 else if (i > 0) { 353 int j = 1; 354 355 /* See if we can resync on a 356 * partial match in our buffer 357 */ 358 while (j < i && memcmp(got + j, str, i - j) != 0) 359 j++; 360 if (j < i) 361 memcpy(got, got + j, i - j); 362 i -= j; 363 } 364 } else 365 r = alarmed ? 3 : 2; 366 } 367 } 368 alarm(0); 369 chat_unalarm(); 370 alarmed = 0; 371 free(got); 372 } 373 } 374 375 if (chat_debug & CHATDEBUG_EXPECT) 376 syslog(LOG_DEBUG, "chat_expect %s", result(r)); 377 378 return r; 379 } 380 381 382 /* 383 * chat_send() 384 * send a chat string 385 */ 386 387 static int 388 chat_send(char const *str) 389 { 390 int r = 0; 391 392 if (chat_debug && CHATDEBUG_SEND) 393 syslog(LOG_DEBUG, "chat_send '%s'", cleanstr(str, strlen(str))); 394 395 if (*str) { 396 alarm(chat_alarm); 397 alarmed = 0; 398 while (r == 0 && *str) 399 { 400 unsigned char ch = (unsigned char)*str++; 401 402 if (alarmed) 403 r = 3; 404 else if (ch == PAUSE_CH) 405 usleep(500000); /* 1/2 second */ 406 else { 407 usleep(10000); /* be kind to modem */ 408 if (write(STDOUT_FILENO, &ch, 1) != 1) 409 r = alarmed ? 3 : 2; 410 } 411 } 412 alarm(0); 413 chat_unalarm(); 414 alarmed = 0; 415 } 416 417 if (chat_debug & CHATDEBUG_SEND) 418 syslog(LOG_DEBUG, "chat_send %s", result(r)); 419 420 return r; 421 } 422 423 424 /* 425 * getty_chat() 426 * 427 * Termination codes: 428 * -1 - no script supplied 429 * 0 - script terminated correctly 430 * 1 - invalid argument, expect string too large, etc. 431 * 2 - error on an I/O operation or fatal error condition 432 * 3 - timeout waiting for a simple string 433 * 434 * Parameters: 435 * char *scrstr - unparsed chat script 436 * timeout - seconds timeout 437 * debug - debug value (bitmask) 438 */ 439 440 int 441 getty_chat(char *scrstr, int timeout, int debug) 442 { 443 int r = -1; 444 445 chat_alarm = timeout ? timeout : CHAT_DEFAULT_TIMEOUT; 446 chat_debug = debug; 447 448 if (scrstr != NULL) { 449 char **script; 450 451 if (chat_debug & CHATDEBUG_MISC) 452 syslog(LOG_DEBUG, "getty_chat script='%s'", scrstr); 453 454 if ((script = read_chat(&scrstr)) != NULL) { 455 int i = r = 0; 456 int off = 0; 457 sig_t old_alarm; 458 459 /* 460 * We need to be in raw mode for all this 461 * Rely on caller... 462 */ 463 464 old_alarm = signal(SIGALRM, chat_alrm); 465 chat_unalarm(); /* Force blocking mode at start */ 466 467 /* 468 * This is the send/expect loop 469 */ 470 while (r == 0 && script[i] != NULL) 471 if ((r = chat_expect(script[i++])) == 0 && script[i] != NULL) 472 r = chat_send(script[i++]); 473 474 signal(SIGALRM, old_alarm); 475 free(script); 476 free(scrstr); 477 478 /* 479 * Ensure stdin is in blocking mode 480 */ 481 ioctl(STDIN_FILENO, FIONBIO, &off); 482 } 483 484 if (chat_debug & CHATDEBUG_MISC) 485 syslog(LOG_DEBUG, "getty_chat %s", result(r)); 486 487 } 488 return r; 489 } 490