xref: /illumos-gate/usr/src/lib/libc/port/gen/getut.c (revision 1c9de0c9)
1 /*
2  * CDDL HEADER START
3  *
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.
7  *
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.
12  *
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]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1988 AT&T	*/
28 /*	  All Rights Reserved  	*/
29 
30 #pragma ident	"%Z%%M%	%I%	%E% SMI"
31 
32 /*
33  * Compatibility routines to read and write alternate
34  * utmp-like files.  These routines are only used in
35  * the case where utmpname() is used to change to a file
36  * other than /var/adm/utmp or /var/adm/wtmp.  In this case,
37  * we assume that someone really wants to read old utmp-format
38  * files.  Otherwise, the getutent, setutent, getutid, setutline,
39  * and pututline functions are actually wrappers around the
40  * equivalent function operating on utmpx-like files.
41  */
42 
43 #include "lint.h"
44 #include <stdio.h>
45 #include <sys/param.h>
46 #include <sys/types.h>
47 #include <sys/stat.h>
48 #include <utmpx.h>
49 #include <errno.h>
50 #include <fcntl.h>
51 #include <string.h>
52 #include <strings.h>
53 #include <stdlib.h>
54 #include <unistd.h>
55 #include <ctype.h>
56 #include <utime.h>
57 #include <sys/wait.h>
58 
59 #define	IDLEN	4	/* length of id field in utmp */
60 #define	SC_WILDC	0xff	/* wild char for utmp ids */
61 #define	MAXVAL	255	/* max value for an id 'character' */
62 
63 #ifdef ut_time
64 #undef ut_time
65 #endif
66 
67 static void	utmp_frec2api(const struct futmp *, struct utmp *);
68 static void	utmp_api2frec(const struct utmp *, struct futmp *);
69 struct utmp 	*_compat_getutent(void);
70 struct utmp	*_compat_getutid(const struct utmp *);
71 struct utmp	*_compat_getutline(const struct utmp *);
72 struct utmp	*_compat_pututline(const struct utmp *);
73 void		_compat_setutent(void);
74 void		_compat_endutent(void);
75 void		_compat_updwtmp(const char *, struct utmp *);
76 struct utmp	*_compat_makeut(struct utmp *);
77 struct utmp	*_compat_modut(struct utmp *);
78 
79 static void	unlockut(void);
80 static int	idcmp(const char *, const char *);
81 static int	allocid(char *, unsigned char *);
82 static int	lockut(void);
83 
84 
85 static int fd = -1;	/* File descriptor for the utmp file. */
86 /*
87  * name of the current utmp-like file - set by utmpname (getutx.c)
88  * only if running in backward compatibility mode
89  */
90 extern const char _compat_utmpfile[];
91 
92 #ifdef ERRDEBUG
93 static long loc_utmp;	/* Where in "utmp" the current "ubuf" was found. */
94 #endif
95 
96 static struct futmp fubuf;	/* Copy of last entry read in. */
97 static struct utmp ubuf;	/* Last entry returned to client */
98 
99 /*
100  * In the 64-bit world, the utmp data structure grows because of
101  * the ut_time field (a time_t) at the end of it.
102  */
103 static void
104 utmp_frec2api(const struct futmp *src, struct utmp *dst)
105 {
106 	if (src == NULL)
107 		return;
108 
109 	(void) strncpy(dst->ut_user, src->ut_user, sizeof (dst->ut_user));
110 	(void) strncpy(dst->ut_line, src->ut_line, sizeof (dst->ut_line));
111 	(void) memcpy(dst->ut_id, src->ut_id, sizeof (dst->ut_id));
112 	dst->ut_pid = src->ut_pid;
113 	dst->ut_type = src->ut_type;
114 	dst->ut_exit.e_termination = src->ut_exit.e_termination;
115 	dst->ut_exit.e_exit = src->ut_exit.e_exit;
116 	dst->ut_time = (time_t)src->ut_time;
117 }
118 
119 static void
120 utmp_api2frec(const struct utmp *src, struct futmp *dst)
121 {
122 	if (src == NULL)
123 		return;
124 
125 	(void) strncpy(dst->ut_user, src->ut_user, sizeof (dst->ut_user));
126 	(void) strncpy(dst->ut_line, src->ut_line, sizeof (dst->ut_line));
127 	(void) memcpy(dst->ut_id, src->ut_id, sizeof (dst->ut_id));
128 	dst->ut_pid = src->ut_pid;
129 	dst->ut_type = src->ut_type;
130 	dst->ut_exit.e_termination = src->ut_exit.e_termination;
131 	dst->ut_exit.e_exit = src->ut_exit.e_exit;
132 	dst->ut_time = (time32_t)src->ut_time;
133 }
134 
135 /*
136  * "getutent_frec" gets the raw version of the next entry in the utmp file.
137  */
138 static struct futmp *
139 getutent_frec(void)
140 {
141 	/*
142 	 * If the "utmp" file is not open, attempt to open it for
143 	 * reading.  If there is no file, attempt to create one.  If
144 	 * both attempts fail, return NULL.  If the file exists, but
145 	 * isn't readable and writeable, do not attempt to create.
146 	 */
147 	if (fd < 0) {
148 		if ((fd = open(_compat_utmpfile, O_RDWR|O_CREAT, 0644)) < 0) {
149 
150 			/*
151 			 * If the open failed for permissions, try opening
152 			 * it only for reading.  All "pututline()" later
153 			 * will fail the writes.
154 			 */
155 			if ((fd = open(_compat_utmpfile, O_RDONLY)) < 0)
156 				return (NULL);
157 		}
158 	}
159 
160 	/* Try to read in the next entry from the utmp file.  */
161 
162 	if (read(fd, &fubuf, sizeof (fubuf)) != sizeof (fubuf)) {
163 		bzero(&fubuf, sizeof (fubuf));
164 		return (NULL);
165 	}
166 
167 	/* Save the location in the file where this entry was found. */
168 
169 	(void) lseek(fd, 0L, 1);
170 	return (&fubuf);
171 }
172 
173 /*
174  * "_compat_getutent" gets the next entry in the utmp file.
175  */
176 struct utmp *
177 _compat_getutent(void)
178 {
179 	struct futmp *futp;
180 
181 	futp = getutent_frec();
182 	utmp_frec2api(&fubuf, &ubuf);
183 	if (futp == NULL)
184 		return (NULL);
185 	return (&ubuf);
186 }
187 
188 /*
189  * "_compat_getutid" finds the specified entry in the utmp file.  If
190  * it can't find it, it returns NULL.
191  */
192 struct utmp *
193 _compat_getutid(const struct utmp *entry)
194 {
195 	short type;
196 
197 	utmp_api2frec(&ubuf, &fubuf);
198 
199 	/*
200 	 * Start looking for entry.  Look in our current buffer before
201 	 * reading in new entries.
202 	 */
203 	do {
204 		/*
205 		 * If there is no entry in "ubuf", skip to the read.
206 		 */
207 		if (fubuf.ut_type != EMPTY) {
208 			switch (entry->ut_type) {
209 
210 			/*
211 			 * Do not look for an entry if the user sent
212 			 * us an EMPTY entry.
213 			 */
214 			case EMPTY:
215 				return (NULL);
216 
217 			/*
218 			 * For RUN_LVL, BOOT_TIME, DOWN_TIME,
219 			 * OLD_TIME, and NEW_TIME entries, only the
220 			 * types have to match.  If they do, return
221 			 * the address of internal buffer.
222 			 */
223 			case RUN_LVL:
224 			case BOOT_TIME:
225 			case DOWN_TIME:
226 			case OLD_TIME:
227 			case NEW_TIME:
228 				if (entry->ut_type == fubuf.ut_type) {
229 					utmp_frec2api(&fubuf, &ubuf);
230 					return (&ubuf);
231 				}
232 				break;
233 
234 			/*
235 			 * For INIT_PROCESS, LOGIN_PROCESS, USER_PROCESS,
236 			 * and DEAD_PROCESS the type of the entry in "fubuf",
237 			 * must be one of the above and id's must match.
238 			 */
239 			case INIT_PROCESS:
240 			case LOGIN_PROCESS:
241 			case USER_PROCESS:
242 			case DEAD_PROCESS:
243 				if (((type = fubuf.ut_type) == INIT_PROCESS ||
244 				    type == LOGIN_PROCESS ||
245 				    type == USER_PROCESS ||
246 				    type == DEAD_PROCESS) &&
247 				    fubuf.ut_id[0] == entry->ut_id[0] &&
248 				    fubuf.ut_id[1] == entry->ut_id[1] &&
249 				    fubuf.ut_id[2] == entry->ut_id[2] &&
250 				    fubuf.ut_id[3] == entry->ut_id[3]) {
251 					utmp_frec2api(&fubuf, &ubuf);
252 					return (&ubuf);
253 				}
254 				break;
255 
256 			/* Do not search for illegal types of entry. */
257 			default:
258 				return (NULL);
259 			}
260 		}
261 	} while (getutent_frec() != NULL);
262 
263 	/* the proper entry wasn't found. */
264 
265 	utmp_frec2api(&fubuf, &ubuf);
266 	return (NULL);
267 }
268 
269 /*
270  * "_compat_getutline" searches the "utmp" file for a LOGIN_PROCESS or
271  * USER_PROCESS with the same "line" as the specified "entry".
272  */
273 struct utmp *
274 _compat_getutline(const struct utmp *entry)
275 {
276 	utmp_api2frec(&ubuf, &fubuf);
277 
278 	do {
279 		/*
280 		 * If the current entry is the one we are interested in,
281 		 * return a pointer to it.
282 		 */
283 		if (fubuf.ut_type != EMPTY &&
284 		    (fubuf.ut_type == LOGIN_PROCESS ||
285 		    fubuf.ut_type == USER_PROCESS) &&
286 		    strncmp(&entry->ut_line[0], &fubuf.ut_line[0],
287 		    sizeof (fubuf.ut_line)) == 0) {
288 			utmp_frec2api(&fubuf, &ubuf);
289 			return (&ubuf);
290 		}
291 	} while (getutent_frec() != NULL);
292 
293 	utmp_frec2api(&fubuf, &ubuf);
294 	return (NULL);
295 }
296 
297 /*
298  * "_compat_pututline" writes the structure sent into the utmp file
299  * If there is already an entry with the same id, then it is
300  * overwritten, otherwise a new entry is made at the end of the
301  * utmp file.
302  */
303 struct utmp *
304 _compat_pututline(const struct utmp *entry)
305 {
306 	int fc;
307 	struct utmp *answer;
308 	struct utmp tmpbuf;
309 	struct futmp ftmpbuf;
310 
311 	/*
312 	 * Copy the user supplied entry into our temporary buffer to
313 	 * avoid the possibility that the user is actually passing us
314 	 * the address of "ubuf".
315 	 */
316 	tmpbuf = *entry;
317 	utmp_api2frec(entry, &ftmpbuf);
318 
319 	(void) getutent_frec();
320 	if (fd < 0) {
321 #ifdef	ERRDEBUG
322 		gdebug("pututline: Unable to create utmp file.\n");
323 #endif
324 		return (NULL);
325 	}
326 
327 	/* Make sure file is writable */
328 
329 	if ((fc = fcntl(fd, F_GETFL, NULL)) == -1 || (fc & O_RDWR) != O_RDWR)
330 		return (NULL);
331 
332 	/*
333 	 * Find the proper entry in the utmp file.  Start at the current
334 	 * location.  If it isn't found from here to the end of the
335 	 * file, then reset to the beginning of the file and try again.
336 	 * If it still isn't found, then write a new entry at the end of
337 	 * the file.  (Making sure the location is an integral number of
338 	 * utmp structures into the file incase the file is scribbled.)
339 	 */
340 
341 	if (_compat_getutid(&tmpbuf) == NULL) {
342 #ifdef	ERRDEBUG
343 		gdebug("1st getutid() failed. fd: %d", fd);
344 #endif
345 		_compat_setutent();
346 		if (_compat_getutid(&tmpbuf) == NULL) {
347 #ifdef	ERRDEBUG
348 			loc_utmp = lseek(fd, 0L, 1);
349 			gdebug("2nd getutid() failed. fd: %d loc_utmp: %ld\n",
350 			    fd, loc_utmp);
351 #endif
352 			(void) fcntl(fd, F_SETFL, fc | O_APPEND);
353 		} else
354 			(void) lseek(fd, -(long)sizeof (struct futmp), 1);
355 	} else
356 		(void) lseek(fd, -(long)sizeof (struct futmp), 1);
357 
358 	/*
359 	 * Write out the user supplied structure.  If the write fails,
360 	 * then the user probably doesn't have permission to write the
361 	 * utmp file.
362 	 */
363 	if (write(fd, &ftmpbuf, sizeof (ftmpbuf)) != sizeof (ftmpbuf)) {
364 #ifdef	ERRDEBUG
365 		gdebug("pututline failed: write-%d\n", errno);
366 #endif
367 		answer = NULL;
368 	} else {
369 		/*
370 		 * Copy the new user structure into ubuf so that it will
371 		 * be up to date in the future.
372 		 */
373 		fubuf = ftmpbuf;
374 		utmp_frec2api(&fubuf, &ubuf);
375 		answer = &ubuf;
376 
377 #ifdef	ERRDEBUG
378 		gdebug("id: %c%c loc: %ld\n", fubuf.ut_id[0],
379 		    fubuf.ut_id[1], fubuf.ut_id[2], fubuf.ut_id[3],
380 		    loc_utmp);
381 #endif
382 	}
383 
384 	(void) fcntl(fd, F_SETFL, fc);
385 
386 	return (answer);
387 }
388 
389 /*
390  * "_compat_setutent" just resets the utmp file back to the beginning.
391  */
392 void
393 _compat_setutent(void)
394 {
395 	if (fd != -1)
396 		(void) lseek(fd, 0L, 0);
397 
398 	/*
399 	 * Zero the stored copy of the last entry read, since we are
400 	 * resetting to the beginning of the file.
401 	 */
402 	bzero(&ubuf, sizeof (ubuf));
403 	bzero(&fubuf, sizeof (fubuf));
404 }
405 
406 /*
407  * "_compat_endutent" closes the utmp file.
408  */
409 void
410 _compat_endutent(void)
411 {
412 	if (fd != -1)
413 		(void) close(fd);
414 	fd = -1;
415 	bzero(&ubuf, sizeof (ubuf));
416 	bzero(&fubuf, sizeof (fubuf));
417 }
418 
419 
420 /*
421  * If one of wtmp and wtmpx files exist, create the other, and the record.
422  * If they both exist add the record.
423  */
424 void
425 _compat_updwtmp(const char *file, struct utmp *ut)
426 {
427 	struct futmp fut;
428 	int fd;
429 
430 
431 	fd = open(file, O_WRONLY | O_APPEND);
432 
433 	if (fd < 0) {
434 		if ((fd = open(file, O_WRONLY|O_CREAT, 0644)) < 0)
435 			return;
436 	}
437 
438 	(void) lseek(fd, 0, 2);
439 
440 	utmp_api2frec(ut, &fut);
441 	(void) write(fd, &fut, sizeof (fut));
442 
443 	(void) close(fd);
444 }
445 
446 
447 
448 /*
449  * makeut - create a utmp entry, recycling an id if a wild card is
450  *	specified.
451  *
452  *	args:	utmp - point to utmp structure to be created
453  */
454 struct utmp *
455 _compat_makeut(struct utmp *utmp)
456 {
457 	int i;
458 	struct utmp *utp;	/* "current" utmp entry being examined */
459 	int wild;		/* flag, true iff wild card char seen */
460 
461 	/* the last id we matched that was NOT a dead proc */
462 	unsigned char saveid[IDLEN];
463 
464 	wild = 0;
465 	for (i = 0; i < IDLEN; i++)
466 		if ((unsigned char)utmp->ut_id[i] == SC_WILDC) {
467 			wild = 1;
468 			break;
469 		}
470 
471 	if (wild) {
472 
473 		/*
474 		 * try to lock the utmp file, only needed if we're
475 		 * doing wildcard matching
476 		 */
477 
478 		if (lockut())
479 			return (0);
480 		_compat_setutent();
481 
482 		/* find the first alphanumeric character */
483 		for (i = 0; i < MAXVAL; ++i)
484 			if (isalnum(i))
485 				break;
486 
487 		(void) memset(saveid, i, IDLEN);
488 
489 		while ((utp = _compat_getutent()) != 0) {
490 			if (idcmp(utmp->ut_id, utp->ut_id))
491 				continue;
492 			if (utp->ut_type == DEAD_PROCESS)
493 				break;
494 			(void) memcpy(saveid, utp->ut_id, IDLEN);
495 		}
496 
497 		if (utp) {
498 			/*
499 			 * found an unused entry, reuse it
500 			 */
501 			(void) memcpy(utmp->ut_id, utp->ut_id, IDLEN);
502 			utp = _compat_pututline(utmp);
503 			if (utp)
504 				_compat_updwtmp(WTMP_FILE, utp);
505 			_compat_endutent();
506 			unlockut();
507 			return (utp);
508 
509 		} else {
510 			/*
511 			 * nothing available, try to allocate an id
512 			 */
513 			if (allocid(utmp->ut_id, saveid)) {
514 				_compat_endutent();
515 				unlockut();
516 				return (NULL);
517 			} else {
518 				utp = _compat_pututline(utmp);
519 				if (utp)
520 					_compat_updwtmp(WTMP_FILE, utp);
521 				_compat_endutent();
522 				unlockut();
523 				return (utp);
524 			}
525 		}
526 	} else {
527 		utp = _compat_pututline(utmp);
528 		if (utp)
529 			_compat_updwtmp(WTMP_FILE, utp);
530 		_compat_endutent();
531 		return (utp);
532 	}
533 }
534 
535 
536 /*
537  * _compat_modut - modify a utmp entry.
538  *
539  *	args:	utmp - point to utmp structure to be created
540  */
541 struct utmp *
542 _compat_modut(struct utmp *utp)
543 {
544 	int i;					/* scratch variable */
545 	struct utmp utmp;			/* holding area */
546 	struct utmp *ucp = &utmp;		/* and a pointer to it */
547 	struct utmp *up;	/* "current" utmp entry being examined */
548 	struct futmp *fup;
549 
550 	for (i = 0; i < IDLEN; ++i)
551 		if ((unsigned char)utp->ut_id[i] == SC_WILDC)
552 			return (0);
553 
554 	/* copy the supplied utmp structure someplace safe */
555 	utmp = *utp;
556 	_compat_setutent();
557 	while (fup = getutent_frec()) {
558 		if (idcmp(ucp->ut_id, fup->ut_id))
559 			continue;
560 		break;
561 	}
562 	up = _compat_pututline(ucp);
563 	if (up)
564 		_compat_updwtmp(WTMP_FILE, up);
565 	_compat_endutent();
566 	return (up);
567 }
568 
569 
570 
571 /*
572  * idcmp - compare two id strings, return 0 if same, non-zero if not *
573  *	args:	s1 - first id string
574  *		s2 - second id string
575  */
576 static int
577 idcmp(const char *s1, const char *s2)
578 {
579 	int i;
580 
581 	for (i = 0; i < IDLEN; ++i)
582 		if ((unsigned char)*s1 != SC_WILDC && (*s1++ != *s2++))
583 			return (-1);
584 	return (0);
585 }
586 
587 
588 /*
589  * allocid - allocate an unused id for utmp, either by recycling a
590  *	DEAD_PROCESS entry or creating a new one.  This routine only
591  *	gets called if a wild card character was specified.
592  *
593  *	args:	srcid - pattern for new id
594  *		saveid - last id matching pattern for a non-dead process
595  */
596 static int
597 allocid(char *srcid, unsigned char *saveid)
598 {
599 	int i;		/* scratch variable */
600 	int changed;	/* flag to indicate that a new id has been generated */
601 	char copyid[IDLEN];	/* work area */
602 
603 	(void) memcpy(copyid, srcid, IDLEN);
604 	changed = 0;
605 	for (i = 0; i < IDLEN; ++i) {
606 		/*
607 		 * if this character isn't wild, it'll
608 		 * be part of the generated id
609 		 */
610 		if ((unsigned char) copyid[i] != SC_WILDC)
611 			continue;
612 		/*
613 		 * it's a wild character, retrieve the
614 		 * character from the saved id
615 		 */
616 		copyid[i] = saveid[i];
617 		/*
618 		 * if we haven't changed anything yet,
619 		 * try to find a new char to use
620 		 */
621 		if (!changed && (saveid[i] < MAXVAL)) {
622 
623 /*
624  * Note: this algorithm is taking the "last matched" id and trying to make
625  * a 1 character change to it to create a new one.  Rather than special-case
626  * the first time (when no perturbation is really necessary), just don't
627  * allocate the first valid id.
628  */
629 
630 			while (++saveid[i] < MAXVAL) {
631 				/* make sure new char is alphanumeric */
632 				if (isalnum(saveid[i])) {
633 					copyid[i] = saveid[i];
634 					changed = 1;
635 					break;
636 				}
637 			}
638 
639 			if (!changed) {
640 				/*
641 				 * Then 'reset' the current count at
642 				 * this position to it's lowest valid
643 				 * value, and propagate the carry to
644 				 * the next wild-card slot
645 				 *
646 				 * See 1113208.
647 				 */
648 				saveid[i] = 0;
649 				while (!isalnum(saveid[i]))
650 					saveid[i]++;
651 				copyid[i] = ++saveid[i];
652 			}
653 		}
654 	}
655 	/* changed is true if we were successful in allocating an id */
656 	if (changed) {
657 		(void) memcpy(srcid, copyid, IDLEN);
658 		return (0);
659 	} else
660 		return (-1);
661 }
662 
663 
664 /*
665  * lockut - lock utmp file
666  */
667 static int
668 lockut(void)
669 {
670 	if ((fd = open(_compat_utmpfile, O_RDWR|O_CREAT, 0644)) < 0)
671 		return (-1);
672 
673 	if (lockf(fd, F_LOCK, 0) < 0) {
674 		(void) close(fd);
675 		fd = -1;
676 		return (-1);
677 	}
678 	return (0);
679 }
680 
681 
682 /*
683  * unlockut - unlock utmp file
684  */
685 static void
686 unlockut(void)
687 {
688 	(void) lockf(fd, F_ULOCK, 0);
689 	(void) close(fd);
690 	fd = -1;
691 }
692 
693 
694 
695 #ifdef  ERRDEBUG
696 
697 #include <stdarg.h>
698 #include <stdio.h>
699 
700 static void
701 gdebug(const char *fmt, ...)
702 {
703 	FILE *fp;
704 	int errnum;
705 	va_list ap;
706 
707 	if ((fp = fopen("/etc/dbg.getut", "a+F")) == NULL)
708 		return;
709 	va_start(ap, fmt);
710 	(void) vfprintf(fp, fmt, ap);
711 	va_end(ap);
712 	(void) fclose(fp);
713 }
714 #endif
715