1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
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 <sys/cdefs.h>
30__FBSDID("$FreeBSD$");
31
32#include "namespace.h"
33#include <sys/endian.h>
34#include <sys/stat.h>
35#include <sys/uio.h>
36#include <errno.h>
37#include <fcntl.h>
38#include <stdio.h>
39#include <string.h>
40#include <unistd.h>
41#include <utmpx.h>
42#include "utxdb.h"
43#include "un-namespace.h"
44
45static FILE *
46futx_open(const char *file)
47{
48	FILE *fp;
49	struct stat sb;
50	int fd;
51
52	fd = _open(file, O_CREAT|O_RDWR|O_EXLOCK|O_CLOEXEC, 0644);
53	if (fd < 0)
54		return (NULL);
55
56	/* Safety check: never use broken files. */
57	if (_fstat(fd, &sb) != -1 && sb.st_size % sizeof(struct futx) != 0) {
58		_close(fd);
59		errno = EFTYPE;
60		return (NULL);
61	}
62
63	fp = fdopen(fd, "r+");
64	if (fp == NULL) {
65		_close(fd);
66		return (NULL);
67	}
68	return (fp);
69}
70
71static int
72utx_active_add(const struct futx *fu)
73{
74	FILE *fp;
75	struct futx fe;
76	off_t partial;
77	int error, ret;
78
79	partial = -1;
80	ret = 0;
81
82	/*
83	 * Register user login sessions.  Overwrite entries of sessions
84	 * that have already been terminated.
85	 */
86	fp = futx_open(_PATH_UTX_ACTIVE);
87	if (fp == NULL)
88		return (-1);
89	while (fread(&fe, sizeof(fe), 1, fp) == 1) {
90		switch (fe.fu_type) {
91		case BOOT_TIME:
92			/* Leave these intact. */
93			break;
94		case USER_PROCESS:
95		case INIT_PROCESS:
96		case LOGIN_PROCESS:
97		case DEAD_PROCESS:
98			/* Overwrite when ut_id matches. */
99			if (memcmp(fu->fu_id, fe.fu_id, sizeof(fe.fu_id)) ==
100			    0) {
101				ret = fseeko(fp, -(off_t)sizeof(fe), SEEK_CUR);
102				goto exact;
103			}
104			if (fe.fu_type != DEAD_PROCESS)
105				break;
106			/* FALLTHROUGH */
107		default:
108			/* Allow us to overwrite unused records. */
109			if (partial == -1) {
110				partial = ftello(fp);
111				/*
112				 * Distinguish errors from valid values so we
113				 * don't overwrite good data by accident.
114				 */
115				if (partial != -1)
116					partial -= (off_t)sizeof(fe);
117			}
118			break;
119		}
120	}
121
122	/*
123	 * No exact match found.  Use the partial match.  If no partial
124	 * match was found, just append a new record.
125	 */
126	if (partial != -1)
127		ret = fseeko(fp, partial, SEEK_SET);
128exact:
129	if (ret == -1)
130		error = errno;
131	else if (fwrite(fu, sizeof(*fu), 1, fp) < 1)
132		error = errno;
133	else
134		error = 0;
135	fclose(fp);
136	if (error != 0)
137		errno = error;
138	return (error == 0 ? 0 : 1);
139}
140
141static int
142utx_active_remove(struct futx *fu)
143{
144	FILE *fp;
145	struct futx fe;
146	int error, ret;
147
148	/*
149	 * Remove user login sessions, having the same ut_id.
150	 */
151	fp = futx_open(_PATH_UTX_ACTIVE);
152	if (fp == NULL)
153		return (-1);
154	error = ESRCH;
155	ret = -1;
156	while (fread(&fe, sizeof(fe), 1, fp) == 1 && ret != 0)
157		switch (fe.fu_type) {
158		case USER_PROCESS:
159		case INIT_PROCESS:
160		case LOGIN_PROCESS:
161			if (memcmp(fu->fu_id, fe.fu_id, sizeof(fe.fu_id)) != 0)
162				continue;
163
164			/* Terminate session. */
165			if (fseeko(fp, -(off_t)sizeof(fe), SEEK_CUR) == -1)
166				error = errno;
167			else if (fwrite(fu, sizeof(*fu), 1, fp) < 1)
168				error = errno;
169			else
170				ret = 0;
171
172		}
173
174	fclose(fp);
175	if (ret != 0)
176		errno = error;
177	return (ret);
178}
179
180static void
181utx_active_init(const struct futx *fu)
182{
183	int fd;
184
185	/* Initialize utx.active with a single BOOT_TIME record. */
186	fd = _open(_PATH_UTX_ACTIVE, O_CREAT|O_RDWR|O_TRUNC, 0644);
187	if (fd < 0)
188		return;
189	_write(fd, fu, sizeof(*fu));
190	_close(fd);
191}
192
193static void
194utx_active_purge(void)
195{
196
197	truncate(_PATH_UTX_ACTIVE, 0);
198}
199
200static int
201utx_lastlogin_add(const struct futx *fu)
202{
203	struct futx fe;
204	FILE *fp;
205	int error, ret;
206
207	ret = 0;
208
209	/*
210	 * Write an entry to lastlogin.  Overwrite the entry if the
211	 * current user already has an entry.  If not, append a new
212	 * entry.
213	 */
214	fp = futx_open(_PATH_UTX_LASTLOGIN);
215	if (fp == NULL)
216		return (-1);
217	while (fread(&fe, sizeof fe, 1, fp) == 1) {
218		if (strncmp(fu->fu_user, fe.fu_user, sizeof fe.fu_user) != 0)
219			continue;
220
221		/* Found a previous lastlogin entry for this user. */
222		ret = fseeko(fp, -(off_t)sizeof fe, SEEK_CUR);
223		break;
224	}
225	if (ret == -1)
226		error = errno;
227	else if (fwrite(fu, sizeof *fu, 1, fp) < 1) {
228		error = errno;
229		ret = -1;
230	}
231	fclose(fp);
232	if (ret == -1)
233		errno = error;
234	return (ret);
235}
236
237static void
238utx_lastlogin_upgrade(void)
239{
240	struct stat sb;
241	int fd;
242
243	fd = _open(_PATH_UTX_LASTLOGIN, O_RDWR|O_CLOEXEC, 0644);
244	if (fd < 0)
245		return;
246
247	/*
248	 * Truncate broken lastlogin files.  In the future we should
249	 * check for older versions of the file format here and try to
250	 * upgrade it.
251	 */
252	if (_fstat(fd, &sb) != -1 && sb.st_size % sizeof(struct futx) != 0)
253		ftruncate(fd, 0);
254	_close(fd);
255}
256
257static int
258utx_log_add(const struct futx *fu)
259{
260	struct iovec vec[2];
261	int error, fd;
262	uint16_t l;
263
264	/*
265	 * Append an entry to the log file.  We only need to append
266	 * records to this file, so to conserve space, trim any trailing
267	 * zero-bytes.  Prepend a length field, indicating the length of
268	 * the record, excluding the length field itself.
269	 */
270	for (l = sizeof(*fu); l > 0 && ((const char *)fu)[l - 1] == '\0'; l--) ;
271	vec[0].iov_base = &l;
272	vec[0].iov_len = sizeof(l);
273	vec[1].iov_base = __DECONST(void *, fu);
274	vec[1].iov_len = l;
275	l = htobe16(l);
276
277	fd = _open(_PATH_UTX_LOG, O_CREAT|O_WRONLY|O_APPEND|O_CLOEXEC, 0644);
278	if (fd < 0)
279		return (-1);
280	if (_writev(fd, vec, 2) == -1)
281		error = errno;
282	else
283		error = 0;
284	_close(fd);
285	if (error != 0)
286		errno = error;
287	return (error == 0 ? 0 : 1);
288}
289
290struct utmpx *
291pututxline(const struct utmpx *utmpx)
292{
293	struct futx fu;
294	int bad;
295
296	bad = 0;
297
298	utx_to_futx(utmpx, &fu);
299
300	switch (fu.fu_type) {
301	case BOOT_TIME:
302		utx_active_init(&fu);
303		utx_lastlogin_upgrade();
304		break;
305	case SHUTDOWN_TIME:
306		utx_active_purge();
307		break;
308	case OLD_TIME:
309	case NEW_TIME:
310		break;
311	case USER_PROCESS:
312		bad |= utx_active_add(&fu);
313		bad |= utx_lastlogin_add(&fu);
314		break;
315#if 0 /* XXX: Are these records of any use to us? */
316	case INIT_PROCESS:
317	case LOGIN_PROCESS:
318		bad |= utx_active_add(&fu);
319		break;
320#endif
321	case DEAD_PROCESS:
322		/*
323		 * In case writing a logout entry fails, never attempt
324		 * to write it to utx.log.  The logout entry's ut_id
325		 * might be invalid.
326		 */
327		if (utx_active_remove(&fu) != 0)
328			return (NULL);
329		break;
330	default:
331		errno = EINVAL;
332		return (NULL);
333	}
334
335	bad |= utx_log_add(&fu);
336	return (bad ? NULL : futx_to_utx(&fu));
337}
338