1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2010 Ed Schouten <ed@FreeBSD.org>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29#include "namespace.h"
30#include <sys/endian.h>
31#include <sys/stat.h>
32#include <sys/uio.h>
33#include <errno.h>
34#include <fcntl.h>
35#include <stdio.h>
36#include <string.h>
37#include <unistd.h>
38#include <utmpx.h>
39#include "utxdb.h"
40#include "un-namespace.h"
41
42static FILE *
43futx_open(const char *file)
44{
45	FILE *fp;
46	struct stat sb;
47	int fd;
48
49	fd = _open(file, O_CREAT|O_RDWR|O_EXLOCK|O_CLOEXEC, 0644);
50	if (fd < 0)
51		return (NULL);
52
53	/* Safety check: never use broken files. */
54	if (_fstat(fd, &sb) != -1 && sb.st_size % sizeof(struct futx) != 0) {
55		_close(fd);
56		errno = EFTYPE;
57		return (NULL);
58	}
59
60	fp = fdopen(fd, "r+");
61	if (fp == NULL) {
62		_close(fd);
63		return (NULL);
64	}
65	return (fp);
66}
67
68static int
69utx_active_add(const struct futx *fu)
70{
71	FILE *fp;
72	struct futx fe;
73	off_t partial;
74	int error, ret;
75
76	partial = -1;
77	ret = 0;
78
79	/*
80	 * Register user login sessions.  Overwrite entries of sessions
81	 * that have already been terminated.
82	 */
83	fp = futx_open(_PATH_UTX_ACTIVE);
84	if (fp == NULL)
85		return (-1);
86	while (fread(&fe, sizeof(fe), 1, fp) == 1) {
87		switch (fe.fu_type) {
88		case BOOT_TIME:
89			/* Leave these intact. */
90			break;
91		case USER_PROCESS:
92		case INIT_PROCESS:
93		case LOGIN_PROCESS:
94		case DEAD_PROCESS:
95			/* Overwrite when ut_id matches. */
96			if (memcmp(fu->fu_id, fe.fu_id, sizeof(fe.fu_id)) ==
97			    0) {
98				ret = fseeko(fp, -(off_t)sizeof(fe), SEEK_CUR);
99				goto exact;
100			}
101			if (fe.fu_type != DEAD_PROCESS)
102				break;
103			/* FALLTHROUGH */
104		default:
105			/* Allow us to overwrite unused records. */
106			if (partial == -1) {
107				partial = ftello(fp);
108				/*
109				 * Distinguish errors from valid values so we
110				 * don't overwrite good data by accident.
111				 */
112				if (partial != -1)
113					partial -= (off_t)sizeof(fe);
114			}
115			break;
116		}
117	}
118
119	/*
120	 * No exact match found.  Use the partial match.  If no partial
121	 * match was found, just append a new record.
122	 */
123	if (partial != -1)
124		ret = fseeko(fp, partial, SEEK_SET);
125exact:
126	if (ret == -1)
127		error = errno;
128	else if (fwrite(fu, sizeof(*fu), 1, fp) < 1)
129		error = errno;
130	else
131		error = 0;
132	fclose(fp);
133	if (error != 0)
134		errno = error;
135	return (error == 0 ? 0 : 1);
136}
137
138static int
139utx_active_remove(struct futx *fu)
140{
141	FILE *fp;
142	struct futx fe;
143	int error, ret;
144
145	/*
146	 * Remove user login sessions, having the same ut_id.
147	 */
148	fp = futx_open(_PATH_UTX_ACTIVE);
149	if (fp == NULL)
150		return (-1);
151	error = ESRCH;
152	ret = -1;
153	while (fread(&fe, sizeof(fe), 1, fp) == 1 && ret != 0)
154		switch (fe.fu_type) {
155		case USER_PROCESS:
156		case INIT_PROCESS:
157		case LOGIN_PROCESS:
158			if (memcmp(fu->fu_id, fe.fu_id, sizeof(fe.fu_id)) != 0)
159				continue;
160
161			/* Terminate session. */
162			if (fseeko(fp, -(off_t)sizeof(fe), SEEK_CUR) == -1)
163				error = errno;
164			else if (fwrite(fu, sizeof(*fu), 1, fp) < 1)
165				error = errno;
166			else
167				ret = 0;
168
169		}
170
171	fclose(fp);
172	if (ret != 0)
173		errno = error;
174	return (ret);
175}
176
177static void
178utx_active_init(const struct futx *fu)
179{
180	int fd;
181
182	/* Initialize utx.active with a single BOOT_TIME record. */
183	fd = _open(_PATH_UTX_ACTIVE, O_CREAT|O_RDWR|O_TRUNC, 0644);
184	if (fd < 0)
185		return;
186	_write(fd, fu, sizeof(*fu));
187	_close(fd);
188}
189
190static void
191utx_active_purge(void)
192{
193
194	truncate(_PATH_UTX_ACTIVE, 0);
195}
196
197static int
198utx_lastlogin_add(const struct futx *fu)
199{
200	struct futx fe;
201	FILE *fp;
202	int error, ret;
203
204	ret = 0;
205
206	/*
207	 * Write an entry to lastlogin.  Overwrite the entry if the
208	 * current user already has an entry.  If not, append a new
209	 * entry.
210	 */
211	fp = futx_open(_PATH_UTX_LASTLOGIN);
212	if (fp == NULL)
213		return (-1);
214	while (fread(&fe, sizeof fe, 1, fp) == 1) {
215		if (strncmp(fu->fu_user, fe.fu_user, sizeof fe.fu_user) != 0)
216			continue;
217
218		/* Found a previous lastlogin entry for this user. */
219		ret = fseeko(fp, -(off_t)sizeof fe, SEEK_CUR);
220		break;
221	}
222	if (ret == -1)
223		error = errno;
224	else if (fwrite(fu, sizeof *fu, 1, fp) < 1) {
225		error = errno;
226		ret = -1;
227	}
228	fclose(fp);
229	if (ret == -1)
230		errno = error;
231	return (ret);
232}
233
234static void
235utx_lastlogin_upgrade(void)
236{
237	struct stat sb;
238	int fd;
239
240	fd = _open(_PATH_UTX_LASTLOGIN, O_RDWR|O_CLOEXEC, 0644);
241	if (fd < 0)
242		return;
243
244	/*
245	 * Truncate broken lastlogin files.  In the future we should
246	 * check for older versions of the file format here and try to
247	 * upgrade it.
248	 */
249	if (_fstat(fd, &sb) != -1 && sb.st_size % sizeof(struct futx) != 0)
250		ftruncate(fd, 0);
251	_close(fd);
252}
253
254static int
255utx_log_add(const struct futx *fu)
256{
257	struct iovec vec[2];
258	int error, fd;
259	uint16_t l;
260
261	/*
262	 * Append an entry to the log file.  We only need to append
263	 * records to this file, so to conserve space, trim any trailing
264	 * zero-bytes.  Prepend a length field, indicating the length of
265	 * the record, excluding the length field itself.
266	 */
267	for (l = sizeof(*fu); l > 0 && ((const char *)fu)[l - 1] == '\0'; l--) ;
268	vec[0].iov_base = &l;
269	vec[0].iov_len = sizeof(l);
270	vec[1].iov_base = __DECONST(void *, fu);
271	vec[1].iov_len = l;
272	l = htobe16(l);
273
274	fd = _open(_PATH_UTX_LOG, O_CREAT|O_WRONLY|O_APPEND|O_CLOEXEC, 0644);
275	if (fd < 0)
276		return (-1);
277	if (_writev(fd, vec, 2) == -1)
278		error = errno;
279	else
280		error = 0;
281	_close(fd);
282	if (error != 0)
283		errno = error;
284	return (error == 0 ? 0 : 1);
285}
286
287struct utmpx *
288pututxline(const struct utmpx *utmpx)
289{
290	struct futx fu;
291	int bad;
292
293	bad = 0;
294
295	utx_to_futx(utmpx, &fu);
296
297	switch (fu.fu_type) {
298	case BOOT_TIME:
299		utx_active_init(&fu);
300		utx_lastlogin_upgrade();
301		break;
302	case SHUTDOWN_TIME:
303		utx_active_purge();
304		break;
305	case OLD_TIME:
306	case NEW_TIME:
307		break;
308	case USER_PROCESS:
309		bad |= utx_active_add(&fu);
310		bad |= utx_lastlogin_add(&fu);
311		break;
312#if 0 /* XXX: Are these records of any use to us? */
313	case INIT_PROCESS:
314	case LOGIN_PROCESS:
315		bad |= utx_active_add(&fu);
316		break;
317#endif
318	case DEAD_PROCESS:
319		/*
320		 * In case writing a logout entry fails, never attempt
321		 * to write it to utx.log.  The logout entry's ut_id
322		 * might be invalid.
323		 */
324		if (utx_active_remove(&fu) != 0)
325			return (NULL);
326		break;
327	default:
328		errno = EINVAL;
329		return (NULL);
330	}
331
332	bad |= utx_log_add(&fu);
333	return (bad ? NULL : futx_to_utx(&fu));
334}
335