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