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