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