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