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