xref: /illumos-gate/usr/src/lib/libc/port/gen/getut.c (revision 9a5d73e03cd3312ddb571a748c40a63c58bd66e5)
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
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
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 *
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 *
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 *
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 *
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 *
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
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
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
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 *
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 *
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()) {
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
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
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
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
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
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