1/*	$NetBSD: utmpentry.c,v 1.22 2021/02/26 02:45:43 christos Exp $	*/
2
3/*-
4 * Copyright (c) 2002 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Christos Zoulas.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#include <sys/cdefs.h>
33#ifndef lint
34__RCSID("$NetBSD: utmpentry.c,v 1.22 2021/02/26 02:45:43 christos Exp $");
35#endif
36
37#include <sys/stat.h>
38
39#include <time.h>
40#include <string.h>
41#include <err.h>
42#include <stdlib.h>
43
44#ifdef SUPPORT_UTMP
45#include <utmp.h>
46#endif
47#ifdef SUPPORT_UTMPX
48#include <utmpx.h>
49#endif
50
51#include "utmpentry.h"
52
53
54/* Fail the compile if x is not true, by constructing an illegal type. */
55#define COMPILE_ASSERT(x) /*LINTED null effect */ \
56	((void)sizeof(struct { unsigned : ((x) ? 1 : -1); }))
57
58
59#ifdef SUPPORT_UTMP
60static void getentry(struct utmpentry *, struct utmp *);
61static struct timespec utmptime = {0, 0};
62#endif
63#ifdef SUPPORT_UTMPX
64static void getentryx(struct utmpentry *, struct utmpx *);
65static struct timespec utmpxtime = {0, 0};
66#endif
67#if defined(SUPPORT_UTMPX) || defined(SUPPORT_UTMP)
68static int setup(const char *);
69static void adjust_size(struct utmpentry *e);
70#endif
71
72size_t maxname = 8, maxline = 8, maxhost = 16;
73int etype = 1 << USER_PROCESS;
74static size_t numutmp = 0;
75static struct utmpentry *ehead;
76
77#if defined(SUPPORT_UTMPX) || defined(SUPPORT_UTMP)
78static void
79adjust_size(struct utmpentry *e)
80{
81	size_t max;
82
83	if ((max = strlen(e->name)) > maxname)
84		maxname = max;
85	if ((max = strlen(e->line)) > maxline)
86		maxline = max;
87	if ((max = strlen(e->host)) > maxhost)
88		maxhost = max;
89}
90
91static int
92setup(const char *fname)
93{
94	int what = 3;
95	struct stat st;
96	const char *sfname;
97
98	if (fname != NULL) {
99		size_t len = strlen(fname);
100		if (len == 0)
101			errx(1, "Filename cannot be 0 length.");
102		what = fname[len - 1] == 'x' ? 1 : 2;
103		if (what == 1) {
104#ifdef SUPPORT_UTMPX
105			if (utmpxname(fname) == 0)
106				warnx("Cannot set utmpx file to `%s'",
107				    fname);
108#else
109			warnx("utmpx support not compiled in");
110#endif
111		} else {
112#ifdef SUPPORT_UTMP
113			if (utmpname(fname) == 0)
114				warnx("Cannot set utmp file to `%s'",
115				    fname);
116#else
117			warnx("utmp support not compiled in");
118#endif
119		}
120	}
121#ifdef SUPPORT_UTMPX
122	if (what & 1) {
123		sfname = fname ? fname : _PATH_UTMPX;
124		if (stat(sfname, &st) == -1) {
125			warn("Cannot stat `%s'", sfname);
126			what &= ~1;
127		} else {
128			if (timespeccmp(&st.st_mtimespec, &utmpxtime, >))
129				utmpxtime = st.st_mtimespec;
130			else
131				what &= ~1;
132		}
133	}
134#endif
135#ifdef SUPPORT_UTMP
136	if (what & 2) {
137		sfname = fname ? fname : _PATH_UTMP;
138		if (stat(sfname, &st) == -1) {
139			warn("Cannot stat `%s'", sfname);
140			what &= ~2;
141		} else {
142			if (timespeccmp(&st.st_mtimespec, &utmptime, >))
143				utmptime = st.st_mtimespec;
144			else
145				what &= ~2;
146		}
147	}
148#endif
149	return what;
150}
151#endif
152
153void
154endutentries(void)
155{
156	struct utmpentry *ep;
157
158#ifdef SUPPORT_UTMP
159	timespecclear(&utmptime);
160#endif
161#ifdef SUPPORT_UTMPX
162	timespecclear(&utmpxtime);
163#endif
164	ep = ehead;
165	while (ep) {
166		struct utmpentry *sep = ep;
167		ep = ep->next;
168		free(sep);
169	}
170	ehead = NULL;
171	numutmp = 0;
172}
173
174size_t
175getutentries(const char *fname, struct utmpentry **epp)
176{
177#ifdef SUPPORT_UTMPX
178	struct utmpx *utx;
179#endif
180#ifdef SUPPORT_UTMP
181	struct utmp *ut;
182#endif
183#if defined(SUPPORT_UTMP) || defined(SUPPORT_UTMPX)
184	struct utmpentry *ep;
185	int what = setup(fname);
186	struct utmpentry **nextp = &ehead;
187	switch (what) {
188	case 0:
189		/* No updates */
190		*epp = ehead;
191		return numutmp;
192	default:
193		/* Need to re-scan */
194		ehead = NULL;
195		numutmp = 0;
196	}
197#endif
198
199#ifdef SUPPORT_UTMPX
200	setutxent();
201	while ((what & 1) && (utx = getutxent()) != NULL) {
202		if (fname == NULL && ((1 << utx->ut_type) & etype) == 0)
203			continue;
204		if ((ep = calloc(1, sizeof(*ep))) == NULL) {
205			warn(NULL);
206			return 0;
207		}
208		getentryx(ep, utx);
209		*nextp = ep;
210		nextp = &(ep->next);
211	}
212#endif
213
214#ifdef SUPPORT_UTMP
215	setutent();
216	if ((etype & (1 << USER_PROCESS)) != 0) {
217		while ((what & 2) && (ut = getutent()) != NULL) {
218			if (fname == NULL && (*ut->ut_name == '\0' ||
219			    *ut->ut_line == '\0'))
220				continue;
221			/* Don't process entries that we have utmpx for */
222			for (ep = ehead; ep != NULL; ep = ep->next) {
223				if (strncmp(ep->line, ut->ut_line,
224				    sizeof(ut->ut_line)) == 0)
225					break;
226			}
227			if (ep != NULL)
228				continue;
229			if ((ep = calloc(1, sizeof(*ep))) == NULL) {
230				warn(NULL);
231				return 0;
232			}
233			getentry(ep, ut);
234			*nextp = ep;
235			nextp = &(ep->next);
236		}
237	}
238#endif
239	numutmp = 0;
240#if defined(SUPPORT_UTMP) || defined(SUPPORT_UTMPX)
241	if (ehead != NULL) {
242		struct utmpentry *from = ehead, *save;
243
244		ehead = NULL;
245		while (from != NULL) {
246			for (nextp = &ehead;
247			    (*nextp) && strcmp(from->line, (*nextp)->line) > 0;
248			    nextp = &(*nextp)->next)
249				continue;
250			save = from;
251			from = from->next;
252			save->next = *nextp;
253			*nextp = save;
254			numutmp++;
255		}
256	}
257	*epp = ehead;
258	return numutmp;
259#else
260	*epp = NULL;
261	return 0;
262#endif
263}
264
265#ifdef SUPPORT_UTMP
266static void
267getentry(struct utmpentry *e, struct utmp *up)
268{
269	COMPILE_ASSERT(sizeof(e->name) > sizeof(up->ut_name));
270	COMPILE_ASSERT(sizeof(e->line) > sizeof(up->ut_line));
271	COMPILE_ASSERT(sizeof(e->host) > sizeof(up->ut_host));
272
273	/*
274	 * e has just been calloc'd. We don't need to clear it or
275	 * append null-terminators, because its length is strictly
276	 * greater than the source string. Use memcpy to _read_
277	 * up->ut_* because they may not be terminated. For this
278	 * reason we use the size of the _source_ as the length
279	 * argument.
280	 */
281	memcpy(e->name, up->ut_name, sizeof(up->ut_name));
282	memcpy(e->line, up->ut_line, sizeof(up->ut_line));
283	memcpy(e->host, up->ut_host, sizeof(up->ut_host));
284
285	e->tv.tv_sec = up->ut_time;
286	e->tv.tv_usec = 0;
287	e->pid = 0;
288	e->term = 0;
289	e->exit = 0;
290	e->sess = 0;
291	e->type = USER_PROCESS;
292	adjust_size(e);
293}
294#endif
295
296#ifdef SUPPORT_UTMPX
297static void
298getentryx(struct utmpentry *e, struct utmpx *up)
299{
300	COMPILE_ASSERT(sizeof(e->name) > sizeof(up->ut_name));
301	COMPILE_ASSERT(sizeof(e->line) > sizeof(up->ut_line));
302	COMPILE_ASSERT(sizeof(e->host) > sizeof(up->ut_host));
303
304	/*
305	 * e has just been calloc'd. We don't need to clear it or
306	 * append null-terminators, because its length is strictly
307	 * greater than the source string. Use memcpy to _read_
308	 * up->ut_* because they may not be terminated. For this
309	 * reason we use the size of the _source_ as the length
310	 * argument.
311	 */
312	memcpy(e->name, up->ut_name, sizeof(up->ut_name));
313	memcpy(e->line, up->ut_line, sizeof(up->ut_line));
314	memcpy(e->host, up->ut_host, sizeof(up->ut_host));
315
316	e->tv = up->ut_tv;
317	e->pid = up->ut_pid;
318	e->term = up->ut_exit.e_termination;
319	e->exit = up->ut_exit.e_exit;
320	e->sess = up->ut_session;
321	e->type = up->ut_type;
322	adjust_size(e);
323}
324#endif
325