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