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 USER_PROCESS:
90		case INIT_PROCESS:
91		case LOGIN_PROCESS:
92		case DEAD_PROCESS:
93			/* Overwrite when ut_id matches. */
94			if (memcmp(fu->fu_id, fe.fu_id, sizeof(fe.fu_id)) ==
95			    0) {
96				ret = fseeko(fp, -(off_t)sizeof(fe), SEEK_CUR);
97				goto exact;
98			}
99			if (fe.fu_type != DEAD_PROCESS)
100				break;
101			/* FALLTHROUGH */
102		default:
103			/* Allow us to overwrite unused records. */
104			if (partial == -1) {
105				partial = ftello(fp);
106				/*
107				 * Distinguish errors from valid values so we
108				 * don't overwrite good data by accident.
109				 */
110				if (partial != -1)
111					partial -= (off_t)sizeof(fe);
112			}
113			break;
114		}
115	}
116
117	/*
118	 * No exact match found.  Use the partial match.  If no partial
119	 * match was found, just append a new record.
120	 */
121	if (partial != -1)
122		ret = fseeko(fp, partial, SEEK_SET);
123exact:
124	if (ret == -1)
125		error = errno;
126	else if (fwrite(fu, sizeof(*fu), 1, fp) < 1)
127		error = errno;
128	else
129		error = 0;
130	fclose(fp);
131	errno = error;
132	return (error == 0 ? 0 : 1);
133}
134
135static int
136utx_active_remove(struct futx *fu)
137{
138	FILE *fp;
139	struct futx fe;
140	int error, ret;
141
142	/*
143	 * Remove user login sessions, having the same ut_id.
144	 */
145	fp = futx_open(_PATH_UTX_ACTIVE);
146	if (fp == NULL)
147		return (-1);
148	error = ESRCH;
149	ret = -1;
150	while (fread(&fe, sizeof(fe), 1, fp) == 1 && ret != 0)
151		switch (fe.fu_type) {
152		case USER_PROCESS:
153		case INIT_PROCESS:
154		case LOGIN_PROCESS:
155			if (memcmp(fu->fu_id, fe.fu_id, sizeof(fe.fu_id)) != 0)
156				continue;
157
158			/* Terminate session. */
159			if (fseeko(fp, -(off_t)sizeof(fe), SEEK_CUR) == -1)
160				error = errno;
161			else if (fwrite(fu, sizeof(*fu), 1, fp) < 1)
162				error = errno;
163			else
164				ret = 0;
165
166		}
167
168	fclose(fp);
169	errno = error;
170	return (ret);
171}
172
173static void
174utx_active_purge(void)
175{
176
177	truncate(_PATH_UTX_ACTIVE, 0);
178}
179
180static int
181utx_lastlogin_add(const struct futx *fu)
182{
183	struct futx fe;
184	FILE *fp;
185	int error, ret;
186
187	ret = 0;
188
189	/*
190	 * Write an entry to lastlogin.  Overwrite the entry if the
191	 * current user already has an entry.  If not, append a new
192	 * entry.
193	 */
194	fp = futx_open(_PATH_UTX_LASTLOGIN);
195	if (fp == NULL)
196		return (-1);
197	while (fread(&fe, sizeof fe, 1, fp) == 1) {
198		if (strncmp(fu->fu_user, fe.fu_user, sizeof fe.fu_user) != 0)
199			continue;
200
201		/* Found a previous lastlogin entry for this user. */
202		ret = fseeko(fp, -(off_t)sizeof fe, SEEK_CUR);
203		break;
204	}
205	if (ret == -1)
206		error = errno;
207	else if (fwrite(fu, sizeof *fu, 1, fp) < 1) {
208		error = errno;
209		ret = -1;
210	}
211	fclose(fp);
212	errno = error;
213	return (ret);
214}
215
216static void
217utx_lastlogin_upgrade(void)
218{
219	struct stat sb;
220	int fd;
221
222	fd = _open(_PATH_UTX_LASTLOGIN, O_RDWR|O_CLOEXEC, 0644);
223	if (fd < 0)
224		return;
225
226	/*
227	 * Truncate broken lastlogin files.  In the future we should
228	 * check for older versions of the file format here and try to
229	 * upgrade it.
230	 */
231	if (_fstat(fd, &sb) != -1 && sb.st_size % sizeof(struct futx) != 0)
232		ftruncate(fd, 0);
233	_close(fd);
234}
235
236static int
237utx_log_add(const struct futx *fu)
238{
239	struct iovec vec[2];
240	int error, fd;
241	uint16_t l;
242
243	/*
244	 * Append an entry to the log file.  We only need to append
245	 * records to this file, so to conserve space, trim any trailing
246	 * zero-bytes.  Prepend a length field, indicating the length of
247	 * the record, excluding the length field itself.
248	 */
249	for (l = sizeof(*fu); l > 0 && ((const char *)fu)[l - 1] == '\0'; l--) ;
250	vec[0].iov_base = &l;
251	vec[0].iov_len = sizeof(l);
252	vec[1].iov_base = __DECONST(void *, fu);
253	vec[1].iov_len = l;
254	l = htobe16(l);
255
256	fd = _open(_PATH_UTX_LOG, O_CREAT|O_WRONLY|O_APPEND|O_CLOEXEC, 0644);
257	if (fd < 0)
258		return (-1);
259	if (_writev(fd, vec, 2) == -1)
260		error = errno;
261	else
262		error = 0;
263	_close(fd);
264	errno = error;
265	return (error == 0 ? 0 : 1);
266}
267
268struct utmpx *
269pututxline(const struct utmpx *utmpx)
270{
271	struct futx fu;
272	int bad;
273
274	bad = 0;
275
276	utx_to_futx(utmpx, &fu);
277
278	switch (fu.fu_type) {
279	case BOOT_TIME:
280	case SHUTDOWN_TIME:
281		utx_active_purge();
282		utx_lastlogin_upgrade();
283		break;
284	case OLD_TIME:
285	case NEW_TIME:
286		break;
287	case USER_PROCESS:
288		bad |= utx_active_add(&fu);
289		bad |= utx_lastlogin_add(&fu);
290		break;
291#if 0 /* XXX: Are these records of any use to us? */
292	case INIT_PROCESS:
293	case LOGIN_PROCESS:
294		bad |= utx_active_add(&fu);
295		break;
296#endif
297	case DEAD_PROCESS:
298		/*
299		 * In case writing a logout entry fails, never attempt
300		 * to write it to utx.log.  The logout entry's ut_id
301		 * might be invalid.
302		 */
303		if (utx_active_remove(&fu) != 0)
304			return (NULL);
305		break;
306	default:
307		errno = EINVAL;
308		return (NULL);
309	}
310
311	bad |= utx_log_add(&fu);
312	return (bad ? NULL : futx_to_utx(&fu));
313}
314