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