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