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