3124 Remove any existing references to utmp, use utmpx instead
[unleashed.git] / usr / src / cmd / bnu / ct.c
blob5dffdc301e536a28da1846af160510a0d323ab7e
1 /*
2 * CDDL HEADER START
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
19 * CDDL HEADER END
22 * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
26 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
27 /* All Rights Reserved */
29 #pragma ident "%Z%%M% %I% %E% SMI"
33 * ct [-h] [-v] [-w n] [-x n] [-s speed] telno ...
35 * dials the given telephone number, waits for the
36 * modem to answer, and initiates a login process.
38 * ct uses several routines from uucp:
39 * - getto(flds) takes a vector of fields needed to make
40 * a connection and returns a file descriptor or -1
41 * - rddev( ... ) takes several arguments and returns lines
42 * from the /etc/uucp/Devices that match the type
43 * (in ct the type will be ACU)
44 * - fdig(string) takes a string that is zero or more
45 * alphabetic characters follow by a number (baud rate)
46 * and returns a pointer to the first digit in the string.
47 * - fn_cklock(dev) takes a device name [/dev/]term/11 and
48 * checks whether the appropriate lock file exists. It returns
49 * FAIL if it does.
50 * - rmlock(pointer) removes the lock file. In ct pointer is
51 * always CNULL (a null pointer) causing rmlock to remove
52 * all lock files associated with this execution of ct.
55 #include "uucp.h"
56 #include "sysfiles.h"
57 #include <pwd.h>
58 #include <utmpx.h>
60 #ifdef DATAKIT
61 #include <dk.h>
62 extern int dkminor();
63 #endif
65 #define ROOT 0
66 #define SYS 3
67 #define TTYGID (gid_t) 7 /* group id for terminal */
68 #define TTYMOD (mode_t) 0622
69 #define DEV "/dev/"
70 #define TELNOSIZE 32 /* maximum phone # size is 31 */
71 #define LEGAL "0123456789-*#="
72 #define USAGE "[-h] [-v] [-w n] [-x n] [-s speed] telno ..."
73 #define LOG "/var/adm/ctlog"
74 #define TTYMON "/usr/lib/saf/ttymon"
75 #define TRUE 1
76 #define FALSE 0
78 static
79 int _Status; /* exit status of child */
81 static
82 pid_t _Pid = 0; /* process id of child */
84 static
85 char
86 _Tty[sizeof DEV+12] = "", /* /dev/term/xx for connection device */
87 *_Dev[D_MAX + 1], /* Filled in by rddev and used globally */
88 _Devbuf[BUFSIZ]; /* buffer for rddev */
90 static
91 char
92 *_Num, /* pointer to a phone number */
93 *_Flds[7]; /* Filled in as if finds() in uucp did it */
95 static
96 time_t _Log_on,
97 _Log_elpsd;
99 static
100 FILE *_Fdl;
102 extern int optind;
103 extern char *optarg, *fdig();
104 extern void cleanup();
105 extern struct passwd *getpwuid ();
107 extern int getto(), rddev();
108 static int gdev(), logproc(), exists();
109 static void startat(), stopat(), disconnect(), zero();
112 * These two dummy routines are needed because the uucp routines
113 * used by ct reference them, but they will never be
114 * called when executing from ct
117 /*VARARGS*/
118 /*ARGSUSED*/
119 void
120 assert (s1, s2, i1, s3, i2)
121 char *s1, *s2, *s3;
122 int i1, i2;
123 { } /* for ASSERT in gnamef.c */
125 /*ARGSUSED*/
126 void
127 logent (s1, s2)
128 char *s1, *s2;
129 { } /* so we can load ulockf() */
131 jmp_buf Sjbuf; /* used by uucp routines */
134 main (argc, argv)
135 int argc;
136 char *argv[];
138 int c;
139 int found = 0,
140 errors = 0,
141 first = TRUE;
142 int count,
143 logprocflag, /* is there a login process on the line */
144 hangup = 1, /* hangup by default */
145 minutes = 0; /* number of minutes to wait for dialer */
146 int fdl;
147 struct termio termio;
148 typedef void (*save_sig)();
149 save_sig save_hup,
150 save_quit,
151 save_int;
152 extern void setservice(), devreset();
153 extern int sysaccess();
155 save_hup = signal (SIGHUP, cleanup);
156 save_quit = signal (SIGQUIT, cleanup);
157 save_int = signal (SIGINT, cleanup);
158 (void) signal (SIGTERM, cleanup);
159 (void) strcpy (Progname, "ct");
161 setservice("cu");
162 if ( sysaccess(EACCESS_DEVICES) != 0 ) {
163 (void) fprintf(stderr, "ct: can't access Devices file\n");
164 cleanup(101);
167 /* Set up the _Flds vector as if finds() [from uucico] built it */
168 _Flds[F_NAME] = "dummy"; /* never used */
169 _Flds[F_TIME] = "Any"; /* never used */
170 _Flds[F_TYPE] = "ACU";
171 _Flds[F_CLASS] = "1200"; /* default at 1200 */
172 _Flds[F_PHONE] = ""; /* filled in by arguments */
173 _Flds[F_LOGIN] = ""; /* never used */
174 _Flds[6] = NULL;
176 while ((c = getopt (argc, argv, "hvw:s:x:")) != EOF) {
177 switch (c) {
178 case 'h':
179 hangup = 0;
180 break;
182 case 'v':
183 Verbose = 1;
184 break;
186 case 'w':
187 minutes = atoi (optarg);
188 if (minutes < 1) {
189 (void) fprintf(stderr,
190 "\tusage: %s %s\n", Progname, USAGE);
191 (void) fprintf(stderr, "(-w %s) Wait time must be > 0\n",
192 optarg);
193 cleanup(101);
195 break;
197 case 's':
198 _Flds[F_CLASS] = optarg;
199 break;
201 case 'x':
202 Debug = atoi(optarg);
203 if (Debug < 0 || Debug > 9) {
204 (void) fprintf(stderr,
205 "\tusage: %s %s\n", Progname, USAGE);
206 (void) fprintf(stderr, "(-x %s) value must be 0-9\n",
207 optarg);
208 cleanup(101);
210 break;
212 case '?':
213 (void) fprintf(stderr, "\tusage: %s %s\n", Progname, USAGE);
214 cleanup(101);
215 /* NOTREACHED */
219 if (optind == argc) {
220 (void) fprintf(stderr, "\tusage: %s %s\n", Progname, USAGE);
221 (void) fprintf(stderr, "No phone numbers specified!\n");
222 cleanup(101);
225 /* check for valid phone number(s) */
226 for (count = argc - 1; count >= optind; --count) {
227 _Num = argv[count];
228 if (strlen(_Num) >= (size_t)(TELNOSIZE - 1)) {
229 (void) fprintf(stderr, "ct: phone number too long -- %s\n", _Num);
230 ++errors;
232 if ((int)strspn(_Num, LEGAL) < (int)strlen(_Num)) {
233 (void) fprintf(stderr, "ct: bad phone number -- %s\n", _Num);
234 ++errors;
237 if (errors)
238 cleanup(101);
240 /************************************************************/
241 /* Begin Loop: Find an available Dialer */
242 /************************************************************/
243 for (count = 0;; count++) { /* count will be wait time after first
244 * time through the loop.
245 * break will be used exit loop.
247 if ( (found = gdev (_Flds)) > 0) { /* found a dialer */
248 (void) fprintf(stdout, "Allocated dialer at %s baud\n",
249 _Flds[F_CLASS]);
250 break;
252 else if (found == 0) { /* no dialers of that on system */
253 (void) fprintf(stdout, "No %s dialers on this system\n",
254 fdig(_Flds[F_CLASS]) );
255 cleanup(101);
258 if (!first) { /* not the first time in loop */
259 VERBOSE("%s busy", (found == -1) ? "Dialer is" : "Dialers are");
260 VERBOSE(" (%d minute(s))\n", count);
261 if (count < minutes) {
262 sleep(60);
263 continue;
265 /* This is the end of the loop - no time left */
266 break;
269 /**************************************************************/
270 /* First time through loop - get wait minutes if no -w option */
271 /**************************************************************/
272 first = FALSE;
273 (void) fprintf(stdout, "The (%d) %s dialer%s busy\n", -found,
274 _Flds[F_CLASS], (found == -1 ? " is" : "s are"));
275 if (minutes) { /* -w already set wait minutes */
276 (void) fprintf(stdout, "Waiting for %d minute%s\n", minutes,
277 (minutes > 1 ? "s" : "") );
278 sleep(60);
279 continue;
282 if (!isatty(0) ) { /* not a terminal - get out */
283 cleanup(101);
286 /* Ask user if she/he wants to wait */
287 (void) fputs("Do you want to wait for dialer? (y for yes): ", stdout);
288 if ((c = getchar ()) == EOF || tolower (c) != 'y')
289 cleanup(101);
290 while ( (c = getchar()) != EOF && c != '\n')
293 (void) fputs ("Time, in minutes? ", stdout);
294 (void) scanf ("%d", &minutes);
295 while ( (c = getchar()) != EOF && c != '\n')
298 if (minutes <= 0)
299 cleanup(101);
301 (void) fputs ("Waiting for dialer\n", stdout);
302 sleep(60);
303 continue;
306 /************************************************************/
307 /* End Loop: Find an available Dialer */
308 /************************************************************/
310 /* check why loop terminated */
311 if (found < 0) { /* no dialer found - get out */
312 (void) fputs("*** TIMEOUT ***\n", stdout);
313 cleanup(101);
316 (void) signal(SIGHUP, SIG_IGN);
317 /* found a dialer. now try to call */
318 if (!isatty(0))
319 hangup = 0;
321 if (hangup) { /* -h option not specified */
322 do {
323 (void) fputs ("Confirm hang-up? (y/n): ", stdout);
324 switch (c=tolower(getchar())) {
325 case EOF:
326 case 'n':
327 cleanup(101);
328 break;
329 case 'y':
330 break;
331 default:
332 while ( c != EOF && c != '\n' )
333 c=getchar();
334 break;
336 } while (c != 'y');
338 /* close stderr if it is not redirected */
339 if ( isatty(2) ) {
340 Verbose = 0;
341 Debug = 0;
342 (void) close (2);
345 (void) ioctl (0, TCGETA, &termio);
346 termio.c_cflag = 0; /* speed to zero for hangup */
347 (void) ioctl (0, TCSETAW, &termio); /* hang up terminal */
348 (void) sleep (5);
350 (void) close(0);
351 (void) close(1);
353 /* Try each phone number until a connection is made, or non work */
354 for (count = optind; count < argc; count++) {
355 /* call getto routine to make connection */
356 _Flds[F_PHONE] = argv[count];
357 rmlock(CNULL); /* remove temporary lock set by gdev */
358 devreset();
359 fdl = getto(_Flds);
360 if (fdl >= 0) {
362 * If there is a login process on the line, get rid
363 * of the lock file quickly so that when the process
364 * reads the first character, the lock file will be gone
365 * indicating that the process should handle the data.
367 if ( (logprocflag = logproc(Dc)) ) /* really an assignment! */
368 rmlock(CNULL);
370 _Fdl = fdopen(fdl, "r+");
371 (void) sprintf(_Tty, "%s%s", DEV, Dc);
372 /* NOTE: Dc is set in the caller routines */
373 break;
377 /* check why the loop ended (connected or no more numbers to try) */
378 if (count == argc)
379 cleanup(101);
381 /****** Successfully made connection ******/
382 VERBOSE("Connected\n%s", "");
384 #ifdef DATAKIT
385 if (!strcmp(_Dev[D_CALLER], "DK")) {
386 strcpy(_Tty, dtnamer(dkminor(fdl)));
387 strcpy(Dc, (strrchr(_Tty, '/')+1));
388 if ((_Fdl = fopen(_Tty, "r+")) == NULL) {
389 (void) fprintf(stderr, "ct: Cannot open %s, errno %d\n",
390 _Tty, errno);
391 cleanup(101);
394 #endif
396 /* ignore some signals if they were ignored upon invocation of ct */
397 /* or else, have them go to graceful disconnect */
398 if (save_hup == SIG_IGN)
399 (void) signal (SIGHUP, SIG_IGN);
400 else
401 (void) signal (SIGHUP, disconnect);
403 if (save_quit == SIG_IGN)
404 (void) signal (SIGQUIT, SIG_IGN);
405 else
406 (void) signal (SIGQUIT, disconnect);
408 if (save_int == SIG_IGN)
409 (void) signal (SIGINT, SIG_IGN);
410 else
411 (void) signal (SIGINT, disconnect);
413 (void) signal (SIGTERM, disconnect);
414 (void) signal (SIGALRM, disconnect);
416 (void) sleep (2); /* time for phone line/modem to settle */
418 _Log_on = time ((time_t *) 0);
421 * if there is a login process on this line,
422 * tell the user to hit a carriage return to make
423 * the waiting process get past the inital read,
424 * Then exit.
426 if (logprocflag) { /* there is a login process on the line */
427 (void) fputs("Hit carriage return ", _Fdl);
428 (void) fclose(_Fdl);
429 CDEBUG(4, "there is a login process; exit\n%s", "");
430 exit(0);
433 CDEBUG(4, "start login process (%s ", TTYMON);
434 CDEBUG(4, "-g -h -t 60 -l %s)\n", fdig(_Flds[F_CLASS]));
435 for (;;) {
436 pid_t w_ret;
437 switch(_Pid = fork()) {
438 case -1: /* fork failed */
439 if ((!hangup || Verbose))
440 (void) fputs ("ct: can't fork for login process\n", stderr);
441 cleanup(101);
442 /*NOTREACHED*/
444 case 0: /* child process */
445 startat ();
446 (void) close(2);
447 /* ttymon will use open fd 0 for connection */
448 if ( fdl != 0 ) {
449 (void) close(0);
450 dup(fdl);
452 (void) signal(SIGHUP, SIG_DFL); /* so child will exit on hangup */
453 (void) execl(TTYMON, "ttymon", "-g", "-h", "-t", "60",
454 "-l", fdig(_Flds[F_CLASS]), (char *) 0);
455 /* exec failed */
456 cleanup(101);
457 /*NOTREACHED*/
459 default: /* parent process */
460 break;
463 /* Parent process */
465 while ((w_ret = wait(&_Status)) != _Pid)
466 if (w_ret == -1 && errno != EINTR) {
467 VERBOSE("ct: wait failed errno=%d\n", errno);
468 cleanup(101);
470 if ((_Status & 0xff00) < 0) {
471 if (!hangup)
472 VERBOSE("ct: can't exec login process\n%s", "");
473 cleanup(101);
476 stopat(_Flds[F_PHONE]);
478 rewind (_Fdl); /* flush line */
479 (void) fputs ("\nReconnect? ", _Fdl);
481 rewind (_Fdl);
482 (void) alarm (20);
483 c = getc (_Fdl);
485 if (c == EOF || tolower (c) == 'n')
486 disconnect (0); /* normal disconnect */
487 while ( (c = getc(_Fdl)) != EOF && c != '\n')
489 (void) alarm (0);
493 static void
494 disconnect (code)
496 struct termio termio;
498 (void) alarm(0);
499 (void) signal (SIGALRM, SIG_IGN);
500 (void) signal (SIGINT, SIG_IGN);
501 (void) signal (SIGTERM, SIG_IGN);
503 _Log_elpsd = time ((time_t *) 0) - _Log_on;
505 (void) ioctl (fileno(_Fdl), TCGETA, &termio);
506 termio.c_cflag = 0; /* speed to zero for hangup */
507 (void) ioctl (fileno(_Fdl), TCSETAW, &termio); /* hang up terminal */
508 (void) fclose (_Fdl);
510 DEBUG(5, "Disconnect(%d)\n", code);
511 VERBOSE("Disconnected\n%s", "");
513 /* For normal disconnect or timeout on "Reconnect?" message,
514 we already cleaned up above */
516 if ((code != 0) && (code != SIGALRM))
517 stopat(_Flds[F_PHONE]);
519 cleanup(code);
523 * clean and exit with "code" status
525 void
526 cleanup (code)
527 int code;
529 CDEBUG(5, "cleanup(%d)\n", code);
530 rmlock (CNULL);
531 if (*_Tty != '\0') {
532 CDEBUG(5, "chmod/chown %s\n", _Tty);
533 if (chown(_Tty , UUCPUID, TTYGID) < 0 ) {
534 CDEBUG(5, "Can't chown to uid=%u, ", UUCPUID);
535 CDEBUG(5, "gid=%u\n", TTYGID);
537 if (chmod(_Tty , TTYMOD) < 0) {
538 CDEBUG(5, "Can't chmod to %lo\n", (unsigned long) TTYMOD);
541 if (_Pid) { /* kill the child process */
542 (void) signal(SIGHUP, SIG_IGN);
543 (void) signal(SIGQUIT, SIG_IGN);
544 (void) kill (_Pid, SIGKILL);
546 exit (code);
549 /* gdev()
550 * Find an available line with a dialer on it.
551 * Set a temporary lock file for the line.
552 * Return:
553 * >0 - got a dialer
554 * <0 - failed - return the number of possible dialers
555 * 0 - not dialers of requested class on the system.
558 static int
559 gdev (flds)
560 char *flds[];
562 int count = 0;
563 extern void devreset();
565 devreset();
566 while (rddev ("ACU", _Dev, _Devbuf, D_MAX) != FAIL) {
567 /* check caller type */
568 if (!EQUALS (flds[F_TYPE] /* "ACU" */, _Dev[D_TYPE]))
569 continue;
570 /* check class, check (and possibly set) speed */
571 if (!EQUALS (flds[F_CLASS] /* speed */, _Dev[D_CLASS]))
572 continue;
573 count++;
575 if (fn_cklock(_Dev[D_LINE]) == FAIL)
576 continue;
578 /* found available dialer and set temporary lock */
579 return (count);
582 return (- count);
586 * Check if there is a login process active on this line.
587 * Return:
588 * 0 - there is no login process on this line
589 * 1 - found a login process on this line
592 static int
593 logproc(line)
594 char *line;
596 struct utmpx *u;
598 while ((u = getutxent()) != NULL) {
599 if (u->ut_type == LOGIN_PROCESS
600 && EQUALS(u->ut_line, line)
601 && EQUALS(u->ut_user, "LOGIN") ) {
602 CDEBUG(7, "ut_line %s, ", u->ut_line);
603 CDEBUG(7, "ut_user %s, ", u->ut_user);
604 CDEBUG(7, "ut_id %.4s, ", u->ut_id);
605 CDEBUG(7, "ut_pid %d\n", u->ut_pid);
607 /* see if the process is still active */
608 if (kill(u->ut_pid, 0) == 0 || errno == EPERM) {
609 CDEBUG(4, "process still active\n%s", "");
610 return(1);
614 return(0);
618 * Create an entry in utmpx file if one does not already exist.
620 static void
621 startat ()
623 struct utmpx utmpxbuf, *u;
624 int fd;
626 /* Set up the prototype for the utmpx structure we want to write. */
628 u = &utmpxbuf;
629 zero (&u -> ut_user[0], sizeof (u -> ut_user));
630 zero (&u -> ut_line[0], sizeof (u -> ut_line));
632 /* Fill in the various fields of the utmpx structure. */
634 u -> ut_id[0] = 'c';
635 u -> ut_id[1] = 't';
636 u -> ut_id[2] = _Tty[strlen(_Tty)-2];
637 u -> ut_id[3] = _Tty[strlen(_Tty)-1];
638 u -> ut_pid = getpid ();
640 u -> ut_exit.e_termination = 0;
641 u -> ut_exit.e_exit = 0;
642 u -> ut_type = INIT_PROCESS;
643 time (&u -> ut_xtime);
644 setutxent (); /* Start at beginning of utmpx file. */
646 /* For INIT_PROCESSes put in the name of the program in the */
647 /* "ut_user" field. */
649 strncpy (&u -> ut_user[0], "ttymon", sizeof (u -> ut_user));
650 strncpy (&u -> ut_line[0], Dc, sizeof (u -> ut_line));
652 /* Write out the updated entry to utmpx file. */
653 pututxline (u);
655 /* Now attempt to add to the end of the wtmpx file. Do not create */
656 /* if it doesn't already exist. Do not overwrite any info already */
657 /* in file. */
659 if ((fd = open(WTMPX_FILE, O_WRONLY | O_APPEND)) != -1) {
660 (void) write(fd, u, sizeof(*u));
661 (void) close(fd);
663 endutxent ();
664 return;
668 * Change utmpx file entry to "dead".
669 * Make entry in ct log.
672 static void
673 stopat (num)
674 char *num;
676 struct utmpx utmpxbuf, *u;
677 int fd;
678 FILE * fp;
680 /* Set up the prototype for the utmpx structure we want to write. */
682 setutxent();
683 u = &utmpxbuf;
684 zero (&u -> ut_user[0], sizeof (u -> ut_user));
685 zero (&u -> ut_line[0], sizeof (u -> ut_line));
687 /* Fill in the various fields of the utmpx structure. */
689 u -> ut_id[0] = 'c';
690 u -> ut_id[1] = 't';
691 u -> ut_id[2] = _Tty[strlen(_Tty)-2];
692 u -> ut_id[3] = _Tty[strlen(_Tty)-1];
693 u -> ut_pid = (pid_t) _Pid;
694 u -> ut_type = USER_PROCESS;
696 /* Find the old entry in the utmpx file with the user name and */
697 /* copy it back. */
699 if (u = getutxid (u)) {
700 utmpxbuf = *u;
701 u = &utmpxbuf;
704 u -> ut_exit.e_termination = _Status & 0xff;
705 u -> ut_exit.e_exit = (_Status >> 8) & 0xff;
706 u -> ut_type = DEAD_PROCESS;
707 time (&u -> ut_xtime);
709 /* Write out the updated entry to utmpx file. */
711 pututxline (u);
713 /* Now attempt to add to the end of the wtmpx file. Do not create */
714 /* if it doesn't already exist. Do not overwrite any info already */
715 /* in file. */
717 if ((fd = open(WTMPX_FILE, O_WRONLY | O_APPEND)) != -1) {
718 (void) write(fd, u, sizeof(*u));
719 (void) close(fd);
721 endutxent ();
723 /* Do the log accounting */
725 if (exists (LOG) && (fp = fopen (LOG, "a")) != NULL) {
726 char *aptr;
727 int hrs,
728 mins,
729 secs;
731 /* ignore user set TZ for logfile purposes */
732 if ( (aptr = getenv ("TZ")) != NULL )
733 *aptr = '\0';
735 (aptr = ctime (&_Log_on))[16] = '\0';
736 hrs = _Log_elpsd / 3600;
737 mins = (_Log_elpsd %= 3600) / 60;
738 secs = _Log_elpsd % 60;
739 (void) fprintf(fp, "%-8s ", getpwuid (getuid ()) -> pw_name);
740 (void) fprintf(fp, "(%4s) %s ", fdig(_Flds[F_CLASS]), aptr);
741 if (hrs)
742 (void) fprintf(fp, "%2d:%.2d", hrs, mins);
743 else
744 (void) fprintf(fp, " %2d", mins);
745 (void) fprintf(fp, ":%.2d %s\n", secs, num);
746 (void) fclose (fp);
748 return;
751 static int
752 exists (file)
753 char *file;
755 struct stat statb;
757 if (stat (file, &statb) == -1 && errno == ENOENT)
758 return (0);
759 return (1);
762 static void
763 zero (adr, size)
764 char *adr;
765 int size;
767 while (size--)
768 *adr++ = '\0';
769 return;