Updated YP code from Theo Deraadt taken from NetBSD. This code was written
[unix-history] / usr.bin / chat / chat.c
CommitLineData
e4b1b510
RG
1/*
2 * Chat -- a program for automatic session establishment (i.e. dial
3 * the phone and log in).
4 *
5 * This software is in the public domain.
6 *
7 * Please send all bug reports, requests for information, etc. to:
8 *
9 * Karl Fox <karl@MorningStar.Com>
10 * Morning Star Technologies, Inc.
11 * 1760 Zollinger Road
12 * Columbus, OH 43221
13 * (614)451-1883
14 */
15
16static char sccs_id[] = "@(#)chat.c 1.7";
17
18#include <stdio.h>
19#include <fcntl.h>
20#include <signal.h>
21#include <errno.h>
22#include <sys/types.h>
23#include <sys/stat.h>
24#include <varargs.h>
25#include <syslog.h>
26
27#ifdef __386BSD__
28#define TERMIOS
29#define SIGHAND_TYPE __sighandler_t
30#else
31#define TERMIO
32#endif
33
34#ifdef sun
35#define SIGHAND_TYPE int (*)()
36# if defined(SUNOS) && SUNOS >= 41
37# ifndef HDB
38# define HDB
39# endif
40# endif
41#endif
42
43#ifdef TERMIO
44#include <termio.h>
45#endif
46#ifdef TERMIOS
47#include <sys/ioctl.h>
48#include <termios.h>
49#endif
50
51#define STR_LEN 1024
52
53#if defined(sun) | defined(SYSV) | defined(POSIX_SOURCE)
54#define SIGTYPE void
55#else
56#define SIGTYPE int
57#endif
58
59/*************** Micro getopt() *********************************************/
60#define OPTION(c,v) (_O&2&&**v?*(*v)++:!c||_O&4?0:(!(_O&1)&& \
61 (--c,++v),_O=4,c&&**v=='-'&&v[0][1]?*++*v=='-'\
62 &&!v[0][1]?(--c,++v,0):(_O=2,*(*v)++):0))
63#define OPTARG(c,v) (_O&2?**v||(++v,--c)?(_O=1,--c,*v++): \
64 (_O=4,(char*)0):(char*)0)
65#define OPTONLYARG(c,v) (_O&2&&**v?(_O=1,--c,*v++):(char*)0)
66#define ARG(c,v) (c?(--c,*v++):(char*)0)
67
68static int _O = 0; /* Internal state */
69/*************** Micro getopt() *********************************************/
70
71char *program_name;
72
73extern char *strcpy(), *strcat(), *malloc();
74extern int strlen();
75#define copyof(s) ((s) ? strcpy(malloc(strlen(s) + 1), s) : (s))
76
77#ifndef LOCK_DIR
78# ifdef HDB
79# define LOCK_DIR "/usr/spool/locks"
80# else /* HDB */
81# define LOCK_DIR "/usr/spool/uucp"
82# endif /* HDB */
83#endif /* LOCK_DIR */
84
85#define MAX_ABORTS 50
86#define DEFAULT_CHAT_TIMEOUT 45
87
88int verbose = 0;
89int quiet = 0;
90char *lock_file = (char *)0;
91int timeout = DEFAULT_CHAT_TIMEOUT;
92
93int have_tty_parameters = 0;
94#ifdef TERMIO
95struct termio saved_tty_parameters;
96#endif
97#ifdef TERMIOS
98struct termios saved_tty_parameters;
99#endif
100
101char *abort_string[MAX_ABORTS], *fail_reason = (char *)0,
102 fail_buffer[50];
103int n_aborts = 0, abort_next = 0, timeout_next = 0;
104
105/*
106 * chat [ -v ] [ -t timeout ] [ -l lock-file ] \
107 * [...[[expect[-say[-expect...]] say expect[-say[-expect]] ...]]]
108 *
109 * Perform a UUCP-dialer-like chat script on stdin and stdout.
110 */
111main(argc, argv)
112int argc;
113char **argv;
114 {
115 int option, n;
116 char *arg;
117
118 program_name = *argv;
119
120 while (option = OPTION(argc, argv))
121 switch (option)
122 {
123 case 'v':
124 ++verbose;
125 break;
126
127 case 'l':
128 if (arg = OPTARG(argc, argv))
129 lock_file = copyof(arg);
130 else
131 usage();
132
133 break;
134
135 case 't':
136 if (arg = OPTARG(argc, argv))
137 timeout = atoi(arg);
138 else
139 usage();
140
141 break;
142
143 default:
144 usage();
145 }
146
147 openlog("chat", LOG_PID | LOG_NDELAY, LOG_LOCAL2);
148
149 if (verbose) {
150 setlogmask(LOG_UPTO(LOG_INFO));
151 } else {
152 setlogmask(LOG_UPTO(LOG_WARNING));
153 }
154
155 init();
156
157 while (arg = ARG(argc, argv))
158 {
159 chat_expect(arg);
160
161 if (arg = ARG(argc, argv))
162 chat_send(arg);
163 }
164
165 terminate(0);
166 }
167
168/*
169 * We got an error parsing the command line.
170 */
171usage()
172 {
173 fprintf(stderr,
174 "Usage: %s [ -v ] [ -l lock-file ] [ -t timeout ] chat-script\n",
175 program_name);
176 exit(1);
177 }
178
179/*
180 * Print a warning message.
181 */
182/*VARARGS1*/
183warn(format, arg1, arg2, arg3, arg4)
184char *format;
185int arg1, arg2, arg3, arg4;
186 {
187 logf("%s: Warning: ", program_name);
188 logf(format, arg1, arg2, arg3, arg4);
189 logf("\n");
190 }
191
192/*
193 * Print an error message and terminate.
194 */
195/*VARARGS1*/
196fatal(format, arg1, arg2, arg3, arg4)
197char *format;
198int arg1, arg2, arg3, arg4;
199 {
200 logf("%s: ", program_name);
201 logf(format, arg1, arg2, arg3, arg4);
202 logf("\n");
203 unlock();
204 terminate(1);
205 }
206
207/*
208 * Print an error message along with the system error message and
209 * terminate.
210 */
211/*VARARGS1*/
212sysfatal(format, arg1, arg2, arg3, arg4)
213char *format;
214int arg1, arg2, arg3, arg4;
215 {
216 char message[STR_LEN];
217
218 sprintf(message, "%s: ", program_name);
219 sprintf(message + strlen(message), format, arg1, arg2, arg3, arg4);
220 perror(message);
221 unlock();
222 terminate(1);
223 }
224
225int alarmed = 0;
226
227SIGTYPE
228 sigalrm()
229{
230 int flags;
231
232 alarm(1); alarmed = 1; /* Reset alarm to avoid race window */
233 signal(SIGALRM, (SIGHAND_TYPE)sigalrm); /* that can cause hanging in read() */
234
235 if ((flags = fcntl(0, F_GETFL, 0)) == -1)
236 sysfatal("Can't get file mode flags on stdin");
237 else
238 if (fcntl(0, F_SETFL, flags | FNDELAY) == -1)
239 sysfatal("Can't set file mode flags on stdin");
240
241 if (verbose)
242 {
243 logf("alarm\n");
244 }
245 }
246
247unalarm()
248 {
249 int flags;
250
251 if ((flags = fcntl(0, F_GETFL, 0)) == -1)
252 sysfatal("Can't get file mode flags on stdin");
253 else
254 if (fcntl(0, F_SETFL, flags & ~FNDELAY) == -1)
255 sysfatal("Can't set file mode flags on stdin");
256 }
257
258SIGTYPE
259 sigint()
260{
261 fatal("SIGINT");
262}
263
264SIGTYPE
265 sigterm()
266{
267 fatal("SIGTERM");
268}
269
270SIGTYPE
271 sighup()
272{
273 fatal("SIGHUP");
274}
275
276init()
277 {
278 signal(SIGINT, (SIGHAND_TYPE)sigint);
279 signal(SIGTERM, (SIGHAND_TYPE)sigterm);
280 signal(SIGHUP, (SIGHAND_TYPE)sighup);
281
282 if (lock_file)
283 lock();
284
285 set_tty_parameters();
286 signal(SIGALRM, (SIGHAND_TYPE)sigalrm);
287 alarm(0); alarmed = 0;
288 }
289
290
291set_tty_parameters()
292 {
293#ifdef TERMIO
294 struct termio t;
295
296 if (ioctl(0, TCGETA, &t) < 0)
297 sysfatal("Can't get terminal parameters");
298#endif
299#ifdef TERMIOS
300 struct termios t;
301
302 if (ioctl(0, TIOCGETA, &t) < 0)
303 sysfatal("Can't get terminal parameters");
304#endif
305
306 saved_tty_parameters = t;
307 have_tty_parameters = 1;
308
309 t.c_iflag = IGNBRK | ISTRIP | IGNPAR;
310 t.c_oflag = 0;
311 t.c_lflag = 0;
312 t.c_cc[VERASE] = t.c_cc[VKILL] = 0;
313 t.c_cc[VMIN] = 1;
314 t.c_cc[VTIME] = 0;
315
316#ifdef TERMIO
317 if (ioctl(0, TCSETA, &t) < 0)
318 sysfatal("Can't set terminal parameters");
319#endif
320#ifdef TERMIOS
321 if (ioctl(0, TIOCSETA, &t) < 0)
322 sysfatal("Can't set terminal parameters");
323#endif
324 }
325
326
327terminate(status)
328{
329 if (have_tty_parameters &&
330#ifdef TERMIO
331 ioctl(0, TCSETA, &saved_tty_parameters) < 0
332#endif
333#ifdef TERMIOS
334 ioctl(0, TIOCSETA, &saved_tty_parameters) < 0
335#endif
336 ) {
337 perror("Can't restore terminal parameters");
338 unlock();
339 exit(1);
340 }
341 exit(status);
342}
343
344/*
345 * Create a lock file for the named lock device
346 */
347lock()
348 {
349 char hdb_lock_buffer[12];
350 int fd, pid;
351
352 lock_file = strcat(strcat(strcpy(malloc(strlen(LOCK_DIR)
353 + 1 + strlen(lock_file) + 1),
354 LOCK_DIR), "/"), lock_file);
355
356 if ((fd = open(lock_file, O_EXCL | O_CREAT | O_RDWR, 0644)) < 0)
357 {
358 char *s = lock_file;
359
360 lock_file = (char *)0; /* Don't remove someone else's lock file! */
361 sysfatal("Can't get lock file '%s'", s);
362 }
363
364# ifdef HDB
365 sprintf(hdb_lock_buffer, "%10d\n", getpid());
366 write(fd, hdb_lock_buffer, 11);
367# else /* HDB */
368 pid = getpid();
369 write(fd, &pid, sizeof pid);
370# endif /* HDB */
371
372 close(fd);
373 }
374
375/*
376 * Remove our lockfile
377 */
378unlock()
379 {
380 if (lock_file)
381 {
382 unlink(lock_file);
383 lock_file = (char *)0;
384 }
385 }
386
387/*
388 * 'Clean up' this string.
389 */
390char *clean(s, sending)
391register char *s;
392int sending;
393 {
394 char temp[STR_LEN];
395 register char *s1;
396 int add_return = sending;
397
398 for (s1 = temp; *s; ++s)
399 switch (*s)
400 {
401 case '\\':
402 switch (*++s)
403 {
404 case '\\':
405 case 'd': if (sending)
406 *s1++ = '\\';
407
408 *s1++ = *s;
409 break;
410
411 case 'q': quiet = ! quiet; break;
412 case 'r': *s1++ = '\r'; break;
413 case 'n': *s1++ = '\n'; break;
414 case 's': *s1++ = ' '; break;
415
416 case 'c': if (sending && s[1] == '\0')
417 add_return = 0;
418 else
419 *s1++ = *s;
420
421 break;
422
423 default: *s1++ = *s;
424 }
425
426 break;
427
428 case '^':
429 *s1++ = (int)(*++s) & 0x1F;
430 break;
431
432 default:
433 *s1++ = *s;
434 }
435
436 if (add_return)
437 *s1++ = '\r';
438
439 *s1 = '\0';
440 return (copyof(temp));
441 }
442
443/*
444 *
445 */
446chat_expect(s)
447register char *s;
448 {
449 if (strcmp(s, "ABORT") == 0)
450 {
451 ++abort_next;
452 return;
453 }
454
455 if (strcmp(s, "TIMEOUT") == 0)
456 {
457 ++timeout_next;
458 return;
459 }
460
461 while (*s)
462 {
463 register char *hyphen;
464
465 for (hyphen = s; *hyphen; ++hyphen)
466 if (*hyphen == '-')
467 if (hyphen == s || hyphen[-1] != '\\')
468 break;
469
470 if (*hyphen == '-')
471 {
472 *hyphen = '\0';
473
474 if (get_string(s))
475 return;
476 else
477 {
478 s = hyphen + 1;
479
480 for (hyphen = s; *hyphen; ++hyphen)
481 if (*hyphen == '-')
482 if (hyphen == s || hyphen[-1] != '\\')
483 break;
484
485 if (*hyphen == '-')
486 {
487 *hyphen = '\0';
488
489 chat_send(s);
490 s = hyphen + 1;
491 }
492 else
493 {
494 chat_send(s);
495 return;
496 }
497 }
498 }
499 else
500 if (get_string(s))
501 return;
502 else
503 {
504 if (fail_reason)
505 logf("Failed(%s)\n", fail_reason);
506 else
507 logf("Failed\n");
508
509 unlock();
510 terminate(1);
511 }
512 }
513 }
514
515char *character(c)
516char c;
517 {
518 static char string[10];
519 char *meta;
520
521 meta = (c & 0x80) ? "M-" : "";
522 c &= 0x7F;
523
524 if (c < 32)
525 sprintf(string, "%s^%c", meta, (int)c + '@');
526 else
527 if (c == 127)
528 sprintf(string, "%s^?", meta);
529 else
530 sprintf(string, "%s%c", meta, c);
531
532 return (string);
533 }
534
535/*
536 *
537 */
538chat_send(s)
539register char *s;
540 {
541 if (abort_next)
542 {
543 char *s1;
544
545 abort_next = 0;
546
547 if (n_aborts >= MAX_ABORTS)
548 fatal("Too many ABORT strings");
549
550 s1 = clean(s, 0);
551
552 if (strlen(s1) > strlen(s))
553 fatal("Illegal ABORT string ('%s')\n", s);
554
555 if (strlen(s1) > sizeof fail_buffer - 1)
556 fatal("Too long ABORT string ('%s')\n", s);
557
558 strcpy(s, s1);
559 abort_string[n_aborts++] = s;
560
561 if (verbose)
562 {
563 register char *s1 = s;
564
565 logf("abort on (");
566
567 for (s1 = s; *s1; ++s1)
568 logf("%s", character(*s1));
569
570 logf(")\n");
571 }
572 }
573 else
574 if (timeout_next)
575 {
576 timeout_next = 0;
577 timeout = atoi(s);
578
579 if (timeout <= 0)
580 timeout = DEFAULT_CHAT_TIMEOUT;
581
582 if (verbose)
583 {
584 logf("timeout set to %d seconds\n", timeout);
585 }
586 }
587 else
588 if ( ! put_string(s))
589 {
590 logf("Failed\n");
591 unlock();
592 terminate(1);
593 }
594 }
595
596int get_char()
597 {
598 int status;
599 char c;
600
601 status = read(0, &c, 1);
602
603 switch (status)
604 {
605 case 1:
606 return ((int)c & 0x7F);
607
608 default:
609 warn("read() on stdin returned %d", status);
610
611 case -1:
612 if ((status = fcntl(0, F_GETFL, 0)) == -1)
613 sysfatal("Can't get file mode flags on stdin");
614 else
615 if (fcntl(0, F_SETFL, status & ~FNDELAY) == -1)
616 sysfatal("Can't set file mode flags on stdin");
617
618 return (-1);
619 }
620 }
621
622int put_char(c)
623char c;
624 {
625 int status;
626
627 delay();
628
629 status = write(1, &c, 1);
630
631 switch (status)
632 {
633 case 1:
634 return (0);
635
636 default:
637 warn("write() on stdout returned %d", status);
638
639 case -1:
640 if ((status = fcntl(0, F_GETFL, 0)) == -1)
641 sysfatal("Can't get file mode flags on stdin");
642 else
643 if (fcntl(0, F_SETFL, status & ~FNDELAY) == -1)
644 sysfatal("Can't set file mode flags on stdin");
645
646 return (-1);
647 }
648 }
649
650int put_string(s)
651register char *s;
652 {
653 s = clean(s, 1);
654
655 if (verbose)
656 {
657 logf("send (");
658
659 if (quiet)
660 logf("??????");
661 else
662 {
663 register char *s1 = s;
664
665 for (s1 = s; *s1; ++s1)
666 logf("%s", character(*s1));
667 }
668
669 logf(")\n");
670 }
671
672 alarm(timeout); alarmed = 0;
673
674 for ( ; *s; ++s)
675 {
676 register char c = *s;
677
678 if (c == '\\')
679 if ((c = *++s) == '\0')
680 break;
681 else
682 if (c == 'd') /* \d -- Delay */
683 {
684 sleep(2);
685 continue;
686 }
687
688 if (alarmed || put_char(*s) < 0)
689 {
690 extern int errno;
691
692 alarm(0); alarmed = 0;
693
694 if (verbose)
695 {
696 if (errno == EINTR || errno == EWOULDBLOCK)
697 logf(" -- write timed out\n");
698 else
699 syslog(LOG_INFO, " -- write failed: %m");
700 }
701
702 return (0);
703 }
704 }
705
706 alarm(0); alarmed = 0;
707 return (1);
708 }
709
710/*
711 * 'Wait for' this string to appear on this file descriptor.
712 */
713int get_string(string)
714register char *string;
715 {
716 char temp[STR_LEN];
717 int c, printed = 0, len;
718 register char *s = temp, *end = s + STR_LEN;
719
720 fail_reason = (char *)0;
721 string = clean(string, 0);
722 len = strlen(string);
723
724 if (verbose)
725 {
726 register char *s1;
727
728 logf("expect (");
729
730 for (s1 = string; *s1; ++s1)
731 logf("%s", character(*s1));
732
733 logf(")\n");
734 }
735
736 if (len == 0)
737 {
738 if (verbose)
739 {
740 logf("got it\n");
741 }
742
743 return (1);
744 }
745
746 alarm(timeout); alarmed = 0;
747
748 while ( ! alarmed && (c = get_char()) >= 0)
749 {
750 int n, abort_len;
751
752 if (verbose)
753 {
754 if (c == '\n')
755 logf("\n");
756 else
757 logf("%s", character(c));
758 }
759
760 *s++ = c;
761
762 if (s >= end)
763 {
764 if (verbose)
765 {
766 logf(" -- too much data\n");
767 }
768
769 alarm(0); alarmed = 0;
770 return (0);
771 }
772
773 if (s - temp >= len &&
774 c == string[len - 1] &&
775 strncmp(s - len, string, len) == 0)
776 {
777 if (verbose)
778 {
779 logf("got it\n");
780 }
781
782 alarm(0); alarmed = 0;
783 return (1);
784 }
785
786 for (n = 0; n < n_aborts; ++n)
787 if (s - temp >= (abort_len = strlen(abort_string[n])) &&
788 strncmp(s - abort_len, abort_string[n], abort_len) == 0)
789 {
790 if (verbose)
791 {
792 logf(" -- failed\n");
793 }
794
795 alarm(0); alarmed = 0;
796 strcpy(fail_reason = fail_buffer, abort_string[n]);
797 return (0);
798 }
799
800 if (alarmed && verbose)
801 warn("Alarm synchronization problem");
802 }
803
804 alarm(0);
805
806 if (verbose && printed)
807 {
808 extern int errno;
809
810 if (alarmed)
811 logf(" -- read timed out\n");
812 else
813 syslog(LOG_INFO, " -- read failed: %m");
814 }
815
816 alarmed = 0;
817 return (0);
818 }
819
820/*
821 * Delay an amount appropriate for between typed characters.
822 */
823delay()
824 {
825 register int i;
826
827# ifdef NO_USLEEP
828 for (i = 0; i < 30000; ++i) /* ... did we just say appropriate? */
829 ;
830# else /* NO_USLEEP */
831 usleep(100);
832# endif /* NO_USLEEP */
833 }
834
835char line[256];
836char *p;
837
838logf(fmt, va_alist)
839char *fmt;
840va_dcl
841{
842 va_list pvar;
843 char buf[256];
844
845 va_start(pvar);
846 vsprintf(buf, fmt, pvar);
847 va_end(pvar);
848
849 p = line + strlen(line);
850 strcat(p, buf);
851
852 if (buf[strlen(buf)-1] == '\n') {
853 syslog(LOG_INFO, "%s", line);
854 line[0] = 0;
855 }
856}