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