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