xref: /freebsd/lib/libc/gen/pututxline.c (revision 32c7dde816fd1d738a48af82bf490307cb7b4739)
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 <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 
45 static FILE *
46 futx_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 
71 static int
72 utx_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);
128 exact:
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 
141 static int
142 utx_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 
180 static void
181 utx_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 
193 static void
194 utx_active_purge(void)
195 {
196 
197 	truncate(_PATH_UTX_ACTIVE, 0);
198 }
199 
200 static int
201 utx_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 
237 static void
238 utx_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 
257 static int
258 utx_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 
290 struct utmpx *
291 pututxline(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