xref: /freebsd/lib/libc/gen/pututxline.c (revision 42821a2fc9aa8656e89d0353ece77c4799e940bb)
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 
43 static FILE *
44 futx_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, 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 
69 static int
70 utx_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);
126 exact:
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 	errno = error;
135 	return (error == 0 ? 0 : 1);
136 }
137 
138 static int
139 utx_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 	errno = error;
173 	return (ret);
174 }
175 
176 static void
177 utx_active_init(const struct futx *fu)
178 {
179 	int fd;
180 
181 	/* Initialize utx.active with a single BOOT_TIME record. */
182 	fd = _open(_PATH_UTX_ACTIVE, O_CREAT|O_RDWR|O_TRUNC, 0644);
183 	if (fd < 0)
184 		return;
185 	_write(fd, fu, sizeof(*fu));
186 	_close(fd);
187 }
188 
189 static void
190 utx_active_purge(void)
191 {
192 
193 	truncate(_PATH_UTX_ACTIVE, 0);
194 }
195 
196 static int
197 utx_lastlogin_add(const struct futx *fu)
198 {
199 	struct futx fe;
200 	FILE *fp;
201 	int error, ret;
202 
203 	ret = 0;
204 
205 	/*
206 	 * Write an entry to lastlogin.  Overwrite the entry if the
207 	 * current user already has an entry.  If not, append a new
208 	 * entry.
209 	 */
210 	fp = futx_open(_PATH_UTX_LASTLOGIN);
211 	if (fp == NULL)
212 		return (-1);
213 	while (fread(&fe, sizeof fe, 1, fp) == 1) {
214 		if (strncmp(fu->fu_user, fe.fu_user, sizeof fe.fu_user) != 0)
215 			continue;
216 
217 		/* Found a previous lastlogin entry for this user. */
218 		ret = fseeko(fp, -(off_t)sizeof fe, SEEK_CUR);
219 		break;
220 	}
221 	if (ret == -1)
222 		error = errno;
223 	else if (fwrite(fu, sizeof *fu, 1, fp) < 1) {
224 		error = errno;
225 		ret = -1;
226 	}
227 	fclose(fp);
228 	errno = error;
229 	return (ret);
230 }
231 
232 static void
233 utx_lastlogin_upgrade(void)
234 {
235 	struct stat sb;
236 	int fd;
237 
238 	fd = _open(_PATH_UTX_LASTLOGIN, O_RDWR, 0644);
239 	if (fd < 0)
240 		return;
241 
242 	/*
243 	 * Truncate broken lastlogin files.  In the future we should
244 	 * check for older versions of the file format here and try to
245 	 * upgrade it.
246 	 */
247 	if (_fstat(fd, &sb) != -1 && sb.st_size % sizeof(struct futx) != 0)
248 		ftruncate(fd, 0);
249 	_close(fd);
250 }
251 
252 static int
253 utx_log_add(const struct futx *fu)
254 {
255 	struct iovec vec[2];
256 	int error, fd;
257 	uint16_t l;
258 
259 	/*
260 	 * Append an entry to the log file.  We only need to append
261 	 * records to this file, so to conserve space, trim any trailing
262 	 * zero-bytes.  Prepend a length field, indicating the length of
263 	 * the record, excluding the length field itself.
264 	 */
265 	for (l = sizeof(*fu); l > 0 && ((const char *)fu)[l - 1] == '\0'; l--) ;
266 	vec[0].iov_base = &l;
267 	vec[0].iov_len = sizeof(l);
268 	vec[1].iov_base = __DECONST(void *, fu);
269 	vec[1].iov_len = l;
270 	l = htobe16(l);
271 
272 	fd = _open(_PATH_UTX_LOG, O_CREAT|O_WRONLY|O_APPEND, 0644);
273 	if (fd < 0)
274 		return (-1);
275 	if (_writev(fd, vec, 2) == -1)
276 		error = errno;
277 	else
278 		error = 0;
279 	_close(fd);
280 	errno = error;
281 	return (error == 0 ? 0 : 1);
282 }
283 
284 struct utmpx *
285 pututxline(const struct utmpx *utmpx)
286 {
287 	struct futx fu;
288 	int bad;
289 
290 	bad = 0;
291 
292 	utx_to_futx(utmpx, &fu);
293 
294 	switch (fu.fu_type) {
295 	case BOOT_TIME:
296 		utx_active_init(&fu);
297 		utx_lastlogin_upgrade();
298 		break;
299 	case SHUTDOWN_TIME:
300 		utx_active_purge();
301 		break;
302 	case OLD_TIME:
303 	case NEW_TIME:
304 		break;
305 	case USER_PROCESS:
306 		bad |= utx_active_add(&fu);
307 		bad |= utx_lastlogin_add(&fu);
308 		break;
309 #if 0 /* XXX: Are these records of any use to us? */
310 	case INIT_PROCESS:
311 	case LOGIN_PROCESS:
312 		bad |= utx_active_add(&fu);
313 		break;
314 #endif
315 	case DEAD_PROCESS:
316 		/*
317 		 * In case writing a logout entry fails, never attempt
318 		 * to write it to utx.log.  The logout entry's ut_id
319 		 * might be invalid.
320 		 */
321 		if (utx_active_remove(&fu) != 0)
322 			return (NULL);
323 		break;
324 	default:
325 		errno = EINVAL;
326 		return (NULL);
327 	}
328 
329 	bad |= utx_log_add(&fu);
330 	return (bad ? NULL : futx_to_utx(&fu));
331 }
332