Merge from vendor branch BSDTAR:
[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.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 }