xref: /dragonfly/usr.bin/tip/unidialer.c (revision d9f85b33)
1 /*
2  * Copyright (c) 1986, 1993
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. Neither the name of the University nor the names of its contributors
14  *    may be used to endorse or promote products derived from this software
15  *    without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  *
29  * @(#)unidialer.c	8.1 (Berkeley) 6/6/93
30  * $FreeBSD: src/usr.bin/tip/libacu/unidialer.c,v 1.7 1999/08/28 01:06:30 peter Exp $
31  */
32 
33 /*
34  * Generalized routines for calling up on a Hayes AT command set based modem.
35  * Control variables are pulled out of a modem caps-style database to
36  * configure the driver for a particular modem.
37  */
38 #include "tip.h"
39 #include "pathnames.h"
40 
41 #include <sys/times.h>
42 #include <assert.h>
43 #include <err.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 
47 #include "acucommon.h"
48 #include "tod.h"
49 
50 /* #define DEBUG */
51 #define	MAXRETRY	5
52 
53 typedef enum
54 {
55 	mpt_notype, mpt_string, mpt_number, mpt_boolean
56 } modem_parm_type_t;
57 
58 typedef struct {
59 	modem_parm_type_t modem_parm_type;
60 	const char *name;
61 	union {
62 		char **string;
63 		unsigned int *number;
64 	} value;
65 	union {
66 		char *string;
67 		unsigned int number;
68 	} default_value;
69 } modem_parm_t;
70 
71 /*
72 	Configuration
73 */
74 static char modem_name [80];
75 static char *dial_command;
76 static char *hangup_command;
77 static char *echo_off_command;
78 static char *reset_command;
79 static char *init_string;
80 static char *escape_sequence;
81 static int hw_flow_control;
82 static int lock_baud;
83 static unsigned int intercharacter_delay;
84 static unsigned int intercommand_delay;
85 static unsigned int escape_guard_time;
86 static unsigned int reset_delay;
87 
88 static int unidialer_dialer(char *num, char *acu);
89 static void unidialer_disconnect(void);
90 static void unidialer_abort(void);
91 static int unidialer_connect(void);
92 static int unidialer_swallow(char *);
93 #ifdef DEBUG
94 static void unidialer_verbose_read (void);
95 #endif
96 static void unidialer_modem_cmd(int fd, const char *cmd);
97 static void unidialer_write(int fd, const char *cp, int n);
98 static void unidialer_write_str(int fd, const char *cp);
99 static void sigALRM(int);
100 static int unidialersync(void);
101 
102 static acu_t unidialer =
103 {
104 	modem_name,
105 	unidialer_dialer,
106 	unidialer_disconnect,
107 	unidialer_abort
108 };
109 
110 /*
111 	Table of parameters kept in modem database
112 */
113 modem_parm_t modem_parms [] = {
114 	{ mpt_string, "dial_command", { &dial_command }, { "ATDT%s\r" } },
115 	{ mpt_string, "hangup_command", { &hangup_command }, { "ATH\r", } },
116 	{ mpt_string, "echo_off_command", { &echo_off_command }, { "ATE0\r" } },
117 	{ mpt_string, "reset_command", { &reset_command }, { "ATZ\r" } },
118 	{ mpt_string, "init_string", { &init_string }, { "AT{ &F\r", } },
119 	{ mpt_string, "escape_sequence", { &escape_sequence }, { "+++" } },
120 	{ mpt_boolean, "hw_flow_control", { (char **)&hw_flow_control }, { NULL } },
121 	{ mpt_boolean, "lock_baud", { (char **)&lock_baud }, { NULL } },
122 	{ mpt_number, "intercharacter_delay", { (char **)&intercharacter_delay }, { (char *)50 } },
123 	{ mpt_number, "intercommand_delay", { (char **)&intercommand_delay }, { (char *)300 } },
124 	{ mpt_number, "escape_guard_time", { (char **)&escape_guard_time }, { (char *)300 } },
125 	{ mpt_number, "reset_delay", { (char **)&reset_delay }, { (char *)3000 } },
126 	{ mpt_notype, NULL, { NULL }, { NULL } }
127 };
128 
129 /*
130 	Global vars
131 */
132 static int timeout = 0;
133 static int connected = 0;
134 static jmp_buf timeoutbuf;
135 
136 #define cgetflag(f)	(cgetcap(bp, f, ':') != NULL)
137 
138 #ifdef DEBUG
139 
140 #define print_str(x) printf (#x " = %s\n", x)
141 #define print_num(x) printf (#x " = %d\n", x)
142 
143 void dumpmodemparms (char *modem)
144 {
145 		printf ("modem parms for %s\n", modem);
146 		print_str (dial_command);
147 		print_str (hangup_command);
148 		print_str (echo_off_command);
149 		print_str (reset_command);
150 		print_str (init_string);
151 		print_str (escape_sequence);
152 		print_num (lock_baud);
153 		print_num (intercharacter_delay);
154 		print_num (intercommand_delay);
155 		print_num (escape_guard_time);
156 		print_num (reset_delay);
157 		printf ("\n");
158 }
159 #endif
160 
161 static int getmodemparms (const char *modem)
162 {
163 	char *bp, *db_array [3], *modempath;
164 	int ndx, stat;
165 	modem_parm_t *mpp;
166 
167 	modempath = getenv ("MODEMS");
168 
169 	ndx = 0;
170 
171 	if (modempath != NULL)
172 		db_array [ndx++] = modempath;
173 
174 	db_array [ndx++] = _PATH_MODEMS;
175 	db_array [ndx] = NULL;
176 
177 	if ((stat = cgetent (&bp, db_array, (char *)modem)) < 0) {
178 		switch (stat) {
179 		case -1:
180 			warnx ("unknown modem %s", modem);
181 			break;
182 		case -2:
183 			warnx ("can't open modem description file");
184 			break;
185 		case -3:
186 			warnx ("possible reference loop in modem description file");
187 			break;
188 		}
189 		return 0;
190 	}
191 	for (mpp = modem_parms; mpp->name; mpp++)
192 	{
193 		switch (mpp->modem_parm_type)
194 		{
195 		case mpt_string:
196 			if (cgetstr (bp, (char *)mpp->name, mpp->value.string) == -1)
197 				*mpp->value.string = mpp->default_value.string;
198 			break;
199 		case mpt_number:
200 		{
201 			long l;
202 			if (cgetnum (bp, (char *)mpp->name, &l) == -1)
203 				*mpp->value.number = mpp->default_value.number;
204 			else
205 				*mpp->value.number = (unsigned int)l;
206 		}
207 			break;
208 		case mpt_boolean:
209 			*mpp->value.number = cgetflag ((char *)mpp->name);
210 			break;
211 		default:
212 			break;
213 		}
214 	}
215 	strncpy (modem_name, modem, sizeof (modem_name) - 1);
216 	modem_name [sizeof (modem_name) - 1] = '\0';
217 	return 1;
218 }
219 
220 /*
221 */
222 acu_t *
223 unidialer_getmodem(const char *modem_name)
224 {
225 	acu_t* rc = NULL;
226 	if (getmodemparms (modem_name))
227 		rc = &unidialer;
228 	return rc;
229 }
230 
231 static int
232 unidialer_modem_ready(void)
233 {
234 	int state;
235 	ioctl (FD, TIOCMGET, &state);
236 	return (state & TIOCM_DSR) ? 1 : 0;
237 }
238 
239 static int unidialer_waitfor_modem_ready (int ms)
240 {
241 	int count;
242 	for (count = 0; count < ms; count += 100)
243 	{
244 		if (unidialer_modem_ready ())
245 		{
246 #ifdef DEBUG
247 			printf ("unidialer_waitfor_modem_ready: modem ready.\n");
248 #endif
249 			break;
250 		}
251 		acu_nap (100);
252 	}
253 	return (count < ms);
254 }
255 
256 static void
257 unidialer_tty_clocal(int flag)
258 {
259 	struct termios t;
260 	tcgetattr (FD, &t);
261 	if (flag)
262 		t.c_cflag |= CLOCAL;
263 	else
264 		t.c_cflag &= ~CLOCAL;
265 	tcsetattr (FD, TCSANOW, &t);
266 }
267 
268 static int
269 unidialer_get_modem_response(char *buf, int bufsz, int response_timeout)
270 {
271 	sig_t f;
272 	char c, *p = buf, *lid = buf + bufsz - 1;
273 	int state;
274 
275 	assert (bufsz > 0);
276 
277 	f = signal (SIGALRM, sigALRM);
278 
279 	timeout = 0;
280 
281 	if (setjmp (timeoutbuf)) {
282 		signal (SIGALRM, f);
283 		*p = '\0';
284 #ifdef DEBUG
285 		printf ("get_response: timeout buf=%s\n", buf);
286 #endif
287 		return (0);
288 	}
289 
290 	ualarm (response_timeout * 1000, 0);
291 
292 	state = 0;
293 
294 	while (1)
295 	{
296 		switch (state)
297 		{
298 			case 0:
299 				if (read (FD, &c, 1) == 1)
300 				{
301 					if (c == '\r')
302 					{
303 						++state;
304 					}
305 					else
306 					{
307 #ifdef DEBUG
308 						printf ("get_response: unexpected char %s.\n", ctrl (c));
309 #endif
310 					}
311 				}
312 				break;
313 
314 			case 1:
315 				if (read (FD, &c, 1) == 1)
316 				{
317 					if (c == '\n')
318 					{
319 #ifdef DEBUG
320 						printf ("get_response: <CRLF> encountered.\n");
321 #endif
322 						++state;
323 					}
324 					else
325 					{
326 							state = 0;
327 #ifdef DEBUG
328 							printf ("get_response: unexpected char %s.\n", ctrl (c));
329 #endif
330 					}
331 				}
332 				break;
333 
334 			case 2:
335 				if (read (FD, &c, 1) == 1)
336 				{
337 					if (c == '\r')
338 						++state;
339 					else if (c >= ' ' && p < lid)
340 						*p++ = c;
341 				}
342 				break;
343 
344 			case 3:
345 				if (read (FD, &c, 1) == 1)
346 				{
347 					if (c == '\n')
348 					{
349 						signal (SIGALRM, f);
350 						/* ualarm (0, 0); */
351 						alarm (0);
352 						*p = '\0';
353 #ifdef DEBUG
354 						printf ("get_response: %s\n", buf);
355 #endif
356 						return (1);
357 					}
358 					else
359 					{
360 						state = 0;
361 						p = buf;
362 					}
363 				}
364 				break;
365 		}
366 	}
367 }
368 
369 static int
370 unidialer_get_okay(int ms)
371 {
372 	int okay;
373 	char buf [BUFSIZ];
374 	okay = unidialer_get_modem_response (buf, sizeof (buf), ms) &&
375 		strcmp (buf, "OK") == 0;
376 	return okay;
377 }
378 
379 static int
380 unidialer_dialer(char *num, char *acu)
381 {
382 	char *cp;
383 	char dial_string [80];
384 	char line [80];
385 
386 #ifdef DEBUG
387 	dumpmodemparms (modem_name);
388 #endif
389 
390 	if (lock_baud) {
391 		int i;
392 		if ((i = speed(number(value(BAUDRATE)))) == 0)
393 			return 0;
394 		ttysetup (i);
395 	}
396 
397 	if (boolean(value(VERBOSE)))
398 		printf("Using \"%s\"\n", acu);
399 
400 	acu_hupcl ();
401 
402 	/*
403 	 * Get in synch.
404 	 */
405 	if (!unidialersync()) {
406 badsynch:
407 		printf("tip: can't synchronize with %s\n", modem_name);
408 		logent(value(HOST), num, modem_name, "can't synch up");
409 		return (0);
410 	}
411 
412 	unidialer_modem_cmd (FD, echo_off_command);	/* turn off echoing */
413 
414 	sleep(1);
415 
416 #ifdef DEBUG
417 	if (boolean(value(VERBOSE)))
418 		unidialer_verbose_read();
419 #endif
420 
421 	acu_flush (); /* flush any clutter */
422 
423 	unidialer_modem_cmd (FD, init_string);
424 
425 	if (!unidialer_get_okay (reset_delay))
426 		goto badsynch;
427 
428 	fflush (stdout);
429 
430 	for (cp = num; *cp; cp++)
431 		if (*cp == '=')
432 			*cp = ',';
433 
434 	(void) sprintf (dial_string, dial_command, num);
435 
436 	unidialer_modem_cmd (FD, dial_string);
437 
438 	connected = unidialer_connect ();
439 
440 	if (connected && hw_flow_control) {
441 		acu_hw_flow_control (hw_flow_control);
442 	}
443 
444 	if (timeout) {
445 		sprintf(line, "%d second dial timeout",
446 			number(value(DIALTIMEOUT)));
447 		logent(value(HOST), num, modem_name, line);
448 	}
449 
450 	if (timeout)
451 		unidialer_disconnect ();
452 
453 	return (connected);
454 }
455 
456 static void
457 unidialer_disconnect(void)
458 {
459 	int okay, retries;
460 
461 	acu_flush (); /* flush any clutter */
462 
463 	unidialer_tty_clocal (TRUE);
464 
465  	/* first hang up the modem*/
466 	ioctl (FD, TIOCCDTR, 0);
467 	acu_nap (250);
468 	ioctl (FD, TIOCSDTR, 0);
469 
470 	/*
471 	 * If AT&D2, then dropping DTR *should* just hangup the modem. But
472 	 * some modems reset anyway; also, the modem may be programmed to reset
473 	 * anyway with AT&D3. Play it safe and wait for the full reset time before
474 	 * proceeding.
475 	 */
476 	acu_nap (reset_delay);
477 
478 	if (!unidialer_waitfor_modem_ready (reset_delay))
479 	{
480 #ifdef DEBUG
481 			printf ("unidialer_disconnect: warning CTS low.\r\n");
482 #endif
483 	}
484 
485 	/*
486 	 * If not strapped for DTR control, try to get command mode.
487 	 */
488 	for (retries = okay = 0; retries < MAXRETRY && !okay; retries++)
489 	{
490 		int timeout_value;
491 		/* flush any clutter */
492 		if (!acu_flush ())
493 		{
494 #ifdef DEBUG
495 			printf ("unidialer_disconnect: warning flush failed.\r\n");
496 #endif
497 		}
498 		timeout_value = escape_guard_time;
499 		timeout_value += (timeout_value * retries / MAXRETRY);
500 		acu_nap (timeout_value);
501 		acu_flush (); /* flush any clutter */
502 		unidialer_modem_cmd (FD, escape_sequence);
503 		acu_nap (timeout_value);
504 		unidialer_modem_cmd (FD, hangup_command);
505 		okay = unidialer_get_okay (reset_delay);
506 	}
507 	if (!okay)
508 	{
509 		logent(value(HOST), "", modem_name, "can't hang up modem");
510 		if (boolean(value(VERBOSE)))
511 			printf("hang up failed\n");
512 	}
513 	(void) acu_flush ();
514 	close (FD);
515 }
516 
517 static void
518 unidialer_abort(void)
519 {
520 	unidialer_write_str (FD, "\r");	/* send anything to abort the call */
521 	unidialer_disconnect ();
522 }
523 
524 static void
525 sigALRM(int signo)
526 {
527 	(void) printf("\07timeout waiting for reply\n");
528 	timeout = 1;
529 	longjmp(timeoutbuf, 1);
530 }
531 
532 static int unidialer_swallow (char *match)
533 {
534 	sig_t f;
535 	char c;
536 
537 	f = signal(SIGALRM, sigALRM);
538 
539 	timeout = 0;
540 
541 	if (setjmp(timeoutbuf)) {
542 		signal(SIGALRM, f);
543 		return (0);
544 	}
545 
546 	alarm(number(value(DIALTIMEOUT)));
547 
548 	do {
549 		if (*match =='\0') {
550 			signal(SIGALRM, f);
551 			alarm (0);
552 			return (1);
553 		}
554 		do {
555 			read (FD, &c, 1);
556 		} while (c == '\0');
557 		c &= 0177;
558 #ifdef DEBUG
559 		if (boolean(value(VERBOSE)))
560 		{
561 			/* putchar(c); */
562 			printf (ctrl (c));
563 		}
564 #endif
565 	} while (c == *match++);
566 	signal(SIGALRM, SIG_DFL);
567 	alarm(0);
568 #ifdef DEBUG
569 	if (boolean(value(VERBOSE)))
570 		fflush (stdout);
571 #endif
572 	return (0);
573 }
574 
575 static struct baud_msg {
576 	char *msg;
577 	int baud;
578 } baud_msg[] = {
579 	{ "",		B300 },
580 	{ " 1200",	B1200 },
581 	{ " 2400",	B2400 },
582 	{ " 9600",	B9600 },
583 	{ " 9600/ARQ",	B9600 },
584 	{ 0,		0 },
585 };
586 
587 static int
588 unidialer_connect(void)
589 {
590 	char c;
591 	int nc, nl, n;
592 	char dialer_buf[64];
593 	struct baud_msg *bm;
594 	sig_t f;
595 
596 	if (unidialer_swallow("\r\n") == 0)
597 		return (0);
598 	f = signal(SIGALRM, sigALRM);
599 again:
600 	nc = 0; nl = sizeof(dialer_buf)-1;
601 	bzero(dialer_buf, sizeof(dialer_buf));
602 	timeout = 0;
603 	for (nc = 0, nl = sizeof(dialer_buf)-1 ; nl > 0 ; nc++, nl--) {
604 		if (setjmp(timeoutbuf))
605 			break;
606 		alarm(number(value(DIALTIMEOUT)));
607 		n = read(FD, &c, 1);
608 		alarm(0);
609 		if (n <= 0)
610 			break;
611 		c &= 0x7f;
612 		if (c == '\r') {
613 			if (unidialer_swallow("\n") == 0)
614 				break;
615 			if (!dialer_buf[0])
616 				goto again;
617 			if (strcmp(dialer_buf, "RINGING") == 0 &&
618 			    boolean(value(VERBOSE))) {
619 #ifdef DEBUG
620 				printf("%s\r\n", dialer_buf);
621 #endif
622 				goto again;
623 			}
624 			if (strncmp(dialer_buf, "CONNECT",
625 				    sizeof("CONNECT")-1) != 0)
626 				break;
627 			if (lock_baud) {
628 				signal(SIGALRM, f);
629 #ifdef DEBUG
630 				if (boolean(value(VERBOSE)))
631 					printf("%s\r\n", dialer_buf);
632 #endif
633 				return (1);
634 			}
635 			for (bm = baud_msg ; bm->msg ; bm++)
636 				if (strcmp(bm->msg, dialer_buf+sizeof("CONNECT")-1) == 0) {
637 					if (!acu_setspeed (bm->baud))
638 						goto error;
639 					signal(SIGALRM, f);
640 #ifdef DEBUG
641 					if (boolean(value(VERBOSE)))
642 						printf("%s\r\n", dialer_buf);
643 #endif
644 					return (1);
645 				}
646 			break;
647 		}
648 		dialer_buf[nc] = c;
649 	}
650 	printf("%s\r\n", dialer_buf);
651 error:
652 	signal(SIGALRM, f);
653 	return (0);
654 }
655 
656 /*
657  * This convoluted piece of code attempts to get
658  * the unidialer in sync.
659  */
660 static int
661 unidialersync(void)
662 {
663 	int already = 0;
664 	int len;
665 	char buf[40];
666 
667 	while (already++ < MAXRETRY) {
668 		acu_nap (intercommand_delay);
669 		acu_flush (); /* flush any clutter */
670 		unidialer_write_str (FD, reset_command); /* reset modem */
671 		bzero(buf, sizeof(buf));
672 		acu_nap (reset_delay);
673 		ioctl (FD, FIONREAD, &len);
674 		if (len) {
675 			len = read(FD, buf, sizeof(buf));
676 #ifdef DEBUG
677 			buf [len] = '\0';
678 			printf("unidialersync (%s): (\"%s\")\n\r", modem_name, buf);
679 #endif
680 			if (index(buf, '0') ||
681 		   	   (index(buf, 'O') && index(buf, 'K')))
682 				return(1);
683 		}
684 		/*
685 		 * If not strapped for DTR control,
686 		 * try to get command mode.
687 		 */
688 		acu_nap (escape_guard_time);
689 		unidialer_write_str (FD, escape_sequence);
690 		acu_nap (escape_guard_time);
691 		unidialer_write_str (FD, hangup_command);
692 		/*
693 		 * Toggle DTR to force anyone off that might have left
694 		 * the modem connected.
695 		 */
696 		acu_nap (escape_guard_time);
697 		ioctl (FD, TIOCCDTR, 0);
698 		acu_nap (1000);
699 		ioctl (FD, TIOCSDTR, 0);
700 	}
701 	acu_nap (intercommand_delay);
702 	unidialer_write_str (FD, reset_command);
703 	return (0);
704 }
705 
706 /*
707 	Send commands to modem; impose delay between commands.
708 */
709 static void unidialer_modem_cmd (int fd, const char *cmd)
710 {
711 	static struct timeval oldt = { 0, 0 };
712 	struct timeval newt;
713 	tod_gettime (&newt);
714 	if (tod_lt (&newt, &oldt))
715 	{
716 		unsigned int naptime;
717 		tod_subfrom (&oldt, newt);
718 		naptime = oldt.tv_sec * 1000 + oldt.tv_usec / 1000;
719 		if (naptime > intercommand_delay)
720 		{
721 #ifdef DEBUG
722 		printf ("unidialer_modem_cmd: suspicious naptime (%u ms)\r\n", naptime);
723 #endif
724 			naptime = intercommand_delay;
725 		}
726 #ifdef DEBUG
727 		printf ("unidialer_modem_cmd: delaying %u ms\r\n", naptime);
728 #endif
729 		acu_nap (naptime);
730 	}
731 	unidialer_write_str (fd, cmd);
732 	tod_gettime (&oldt);
733 	newt.tv_sec = 0;
734 	newt.tv_usec = intercommand_delay;
735 	tod_addto (&oldt, &newt);
736 }
737 
738 static void unidialer_write_str (int fd, const char *cp)
739 {
740 #ifdef DEBUG
741 	printf ("unidialer (%s): sending %s\n", modem_name, cp);
742 #endif
743 	unidialer_write (fd, cp, strlen (cp));
744 }
745 
746 static void unidialer_write (int fd, const char *cp, int n)
747 {
748 	acu_nap (intercharacter_delay);
749 	for ( ; n-- ; cp++) {
750 		write (fd, cp, 1);
751 		acu_nap (intercharacter_delay);
752 	}
753 }
754 
755 #ifdef DEBUG
756 static void unidialer_verbose_read(void)
757 {
758 	int n = 0;
759 	char buf[BUFSIZ];
760 
761 	if (ioctl(FD, FIONREAD, &n) < 0)
762 		return;
763 	if (n <= 0)
764 		return;
765 	if (read(FD, buf, n) != n)
766 		return;
767 	write(1, buf, n);
768 }
769 #endif
770 
771 /* end of unidialer.c */
772