xref: /freebsd/contrib/openbsm/bin/auditdistd/trail.c (revision f4b37ed0f8b307b1f3f0f630ca725d68f1dff30d)
1 /*-
2  * Copyright (c) 2012 The FreeBSD Foundation
3  * All rights reserved.
4  *
5  * This software was developed by Pawel Jakub Dawidek under sponsorship from
6  * the FreeBSD Foundation.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  *
29  * $P4: //depot/projects/trustedbsd/openbsm/bin/auditdistd/trail.c#3 $
30  */
31 
32 #include <config/config.h>
33 
34 #include <sys/param.h>
35 #include <sys/stat.h>
36 
37 #include <dirent.h>
38 #include <errno.h>
39 #include <fcntl.h>
40 #include <stdbool.h>
41 #include <stdint.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <unistd.h>
45 
46 #include <compat/compat.h>
47 #ifndef HAVE_STRLCPY
48 #include <compat/strlcpy.h>
49 #endif
50 #ifndef HAVE_FACCESSAT
51 #include "faccessat.h"
52 #endif
53 #ifndef HAVE_FSTATAT
54 #include "fstatat.h"
55 #endif
56 #ifndef HAVE_OPENAT
57 #include "openat.h"
58 #endif
59 #ifndef HAVE_UNLINKAT
60 #include "unlinkat.h"
61 #endif
62 
63 #include "pjdlog.h"
64 #include "trail.h"
65 
66 #define	TRAIL_MAGIC	0x79a11
67 struct trail {
68 	int	 tr_magic;
69 	/* Path usually to /var/audit/dist/ directory. */
70 	char	 tr_dirname[PATH_MAX];
71 	/* Descriptor to td_dirname directory. */
72 	DIR	*tr_dirfp;
73 	/* Path to audit trail file. */
74 	char	 tr_filename[PATH_MAX];
75 	/* Descriptor to audit trail file. */
76 	int	 tr_filefd;
77 };
78 
79 #define	HALF_LEN	14
80 
81 bool
82 trail_is_not_terminated(const char *filename)
83 {
84 
85 	return (strcmp(filename + HALF_LEN, ".not_terminated") == 0);
86 }
87 
88 bool
89 trail_is_crash_recovery(const char *filename)
90 {
91 
92 	return (strcmp(filename + HALF_LEN, ".crash_recovery") == 0);
93 }
94 
95 struct trail *
96 trail_new(const char *dirname, bool create)
97 {
98 	struct trail *trail;
99 
100 	trail = calloc(1, sizeof(*trail));
101 
102 	if (strlcpy(trail->tr_dirname, dirname, sizeof(trail->tr_dirname)) >=
103 	    sizeof(trail->tr_dirname)) {
104 		free(trail);
105 		pjdlog_error("Directory name too long (\"%s\").", dirname);
106 		errno = ENAMETOOLONG;
107 		return (NULL);
108 	}
109 	trail->tr_dirfp = opendir(dirname);
110 	if (trail->tr_dirfp == NULL) {
111 		if (create && errno == ENOENT) {
112 			if (mkdir(dirname, 0700) == -1) {
113 				pjdlog_errno(LOG_ERR,
114 				    "Unable to create directory \"%s\"",
115 				    dirname);
116 				free(trail);
117 				return (NULL);
118 			}
119 			/* TODO: Set directory ownership. */
120 		} else {
121 			pjdlog_errno(LOG_ERR,
122 			    "Unable to open directory \"%s\"",
123 			    dirname);
124 			free(trail);
125 			return (NULL);
126 		}
127 		trail->tr_dirfp = opendir(dirname);
128 		if (trail->tr_dirfp == NULL) {
129 			pjdlog_errno(LOG_ERR,
130 			    "Unable to open directory \"%s\"",
131 			    dirname);
132 			free(trail);
133 			return (NULL);
134 		}
135 	}
136 	trail->tr_filefd = -1;
137 	trail->tr_magic = TRAIL_MAGIC;
138 	return (trail);
139 }
140 
141 void
142 trail_free(struct trail *trail)
143 {
144 
145 	PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC);
146 
147 	if (trail->tr_filefd != -1)
148 		trail_close(trail);
149 	closedir(trail->tr_dirfp);
150 	bzero(trail, sizeof(*trail));
151 	trail->tr_magic = 0;
152 	trail->tr_filefd = -1;
153 	free(trail);
154 }
155 
156 static uint8_t
157 trail_type(DIR *dirfp, const char *filename)
158 {
159 	struct stat sb;
160 	int dfd;
161 
162 	PJDLOG_ASSERT(dirfp != NULL);
163 
164 	dfd = dirfd(dirfp);
165 	PJDLOG_ASSERT(dfd >= 0);
166 	if (fstatat(dfd, filename, &sb, AT_SYMLINK_NOFOLLOW) == -1) {
167 		pjdlog_errno(LOG_ERR, "Unable to stat \"%s\"", filename);
168 		return (DT_UNKNOWN);
169 	}
170 	return (IFTODT(sb.st_mode));
171 }
172 
173 /*
174  * Find trail file by first part of the name in case it was renamed.
175  * First part of the trail file name never changes, but trail file
176  * can be renamed when hosts are disconnected from .not_terminated
177  * to .[0-9]{14} or to .crash_recovery.
178  */
179 static bool
180 trail_find(struct trail *trail)
181 {
182 	struct dirent *dp;
183 
184 	PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC);
185 	PJDLOG_ASSERT(trail_is_not_terminated(trail->tr_filename));
186 
187 	rewinddir(trail->tr_dirfp);
188 	while ((dp = readdir(trail->tr_dirfp)) != NULL) {
189 		if (strncmp(dp->d_name, trail->tr_filename, HALF_LEN + 1) == 0)
190 			break;
191 	}
192 	if (dp == NULL)
193 		return (false);
194 	PJDLOG_VERIFY(strlcpy(trail->tr_filename, dp->d_name,
195 	    sizeof(trail->tr_filename)) < sizeof(trail->tr_filename));
196 	return (true);
197 }
198 
199 /*
200  * Open the given trail file and move pointer at the given offset, as this is
201  * where receiver finished the last time.
202  * If the file doesn't exist or the given offset is equal to the file size,
203  * move to the next trail file.
204  */
205 void
206 trail_start(struct trail *trail, const char *filename, off_t offset)
207 {
208 	struct stat sb;
209 	int dfd, fd;
210 
211 	PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC);
212 
213 	PJDLOG_VERIFY(strlcpy(trail->tr_filename, filename,
214 	    sizeof(trail->tr_filename)) < sizeof(trail->tr_filename));
215 	trail->tr_filefd = -1;
216 
217 	if (trail->tr_filename[0] == '\0') {
218 		PJDLOG_ASSERT(offset == 0);
219 		trail_next(trail);
220 		return;
221 	}
222 
223 	dfd = dirfd(trail->tr_dirfp);
224 	PJDLOG_ASSERT(dfd >= 0);
225 again:
226 	fd = openat(dfd, trail->tr_filename, O_RDONLY);
227 	if (fd == -1) {
228 		if (errno == ENOENT &&
229 		    trail_is_not_terminated(trail->tr_filename) &&
230 		    trail_find(trail)) {
231 			/* File was renamed. Retry with new name. */
232 			pjdlog_debug(1,
233 			   "Trail file was renamed since last connection to \"%s/%s\".",
234 			   trail->tr_dirname, trail->tr_filename);
235 			goto again;
236 		} else if (errno == ENOENT) {
237 			/* File disappeared. */
238 			pjdlog_debug(1, "File \"%s/%s\" doesn't exist.",
239 			    trail->tr_dirname, trail->tr_filename);
240 		} else {
241 			pjdlog_errno(LOG_ERR,
242 			    "Unable to open file \"%s/%s\", skipping",
243 			    trail->tr_dirname, trail->tr_filename);
244 		}
245 		trail_next(trail);
246 		return;
247 	}
248 	if (fstat(fd, &sb) == -1) {
249 		pjdlog_errno(LOG_ERR,
250 		    "Unable to stat file \"%s/%s\", skipping",
251 		    trail->tr_dirname, trail->tr_filename);
252 		close(fd);
253 		trail_next(trail);
254 		return;
255 	}
256 	if (!S_ISREG(sb.st_mode)) {
257 		pjdlog_warning("File \"%s/%s\" is not a regular file, skipping.",
258 		    trail->tr_dirname, trail->tr_filename);
259 		close(fd);
260 		trail_next(trail);
261 		return;
262 	}
263 	/*
264 	 * We continue sending requested file if:
265 	 * 1. It is not fully sent yet, or
266 	 * 2. It is fully sent, but is not terminated, so new data can be
267 	 *    appended still, or
268 	 * 3. It is fully sent but file name has changed.
269 	 *
270 	 * Note that we are fine if our .not_terminated or .crash_recovery file
271 	 * is smaller than the one on the receiver side, as it is possible that
272 	 * more data was send to the receiver than was safely stored on disk.
273 	 * We accept .not_terminated only because auditdistd can start before
274 	 * auditd manage to rename it to .crash_recovery.
275 	 */
276 	if (offset < sb.st_size ||
277 	    (offset >= sb.st_size &&
278 	     trail_is_not_terminated(trail->tr_filename)) ||
279 	    (offset >= sb.st_size && trail_is_not_terminated(filename) &&
280 	     trail_is_crash_recovery(trail->tr_filename))) {
281 		/* File was not fully send. Let's finish it. */
282 		if (lseek(fd, offset, SEEK_SET) == -1) {
283 			pjdlog_errno(LOG_ERR,
284 			    "Unable to move to offset %jd within file \"%s/%s\", skipping",
285 			    (intmax_t)offset, trail->tr_dirname,
286 			    trail->tr_filename);
287 			close(fd);
288 			trail_next(trail);
289 			return;
290 		}
291 		if (!trail_is_crash_recovery(trail->tr_filename)) {
292 			pjdlog_debug(1,
293 			    "Restarting file \"%s/%s\" at offset %jd.",
294 			    trail->tr_dirname, trail->tr_filename,
295 			    (intmax_t)offset);
296 		}
297 		trail->tr_filefd = fd;
298 		return;
299 	}
300 	close(fd);
301 	if (offset > sb.st_size) {
302 		pjdlog_warning("File \"%s/%s\" shrinked, removing it.",
303 		    trail->tr_dirname, trail->tr_filename);
304 	} else {
305 		pjdlog_debug(1, "File \"%s/%s\" is already sent, removing it.",
306 		    trail->tr_dirname, trail->tr_filename);
307 	}
308 	/* Entire file is already sent or it shirnked, we can remove it. */
309 	if (unlinkat(dfd, trail->tr_filename, 0) == -1) {
310 		pjdlog_errno(LOG_WARNING, "Unable to remove file \"%s/%s\"",
311 		    trail->tr_dirname, trail->tr_filename);
312 	}
313 	trail_next(trail);
314 }
315 
316 /*
317  * Set next file in the trail->tr_dirname directory and open it for reading.
318  */
319 void
320 trail_next(struct trail *trail)
321 {
322 	char curfile[PATH_MAX];
323 	struct dirent *dp;
324 	int dfd;
325 
326 	PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC);
327 	PJDLOG_ASSERT(trail->tr_filefd == -1);
328 
329 again:
330 	curfile[0] = '\0';
331 
332 	rewinddir(trail->tr_dirfp);
333 	while ((dp = readdir(trail->tr_dirfp)) != NULL) {
334 		if (dp->d_name[0] < '0' || dp->d_name[0] > '9')
335 			continue;
336 		if (dp->d_type == DT_UNKNOWN)
337 			dp->d_type = trail_type(trail->tr_dirfp, dp->d_name);
338 		/* We are only interested in regular files, skip the rest. */
339 		if (dp->d_type != DT_REG) {
340 			pjdlog_debug(1,
341 			    "File \"%s/%s\" is not a regular file, skipping.",
342 			    trail->tr_dirname, dp->d_name);
343 			continue;
344 		}
345 		/* Skip all files "greater" than curfile. */
346 		if (curfile[0] != '\0' && strcmp(dp->d_name, curfile) > 0)
347 			continue;
348 		/* Skip all files "smaller" than the current trail_filename. */
349 		if (trail->tr_filename[0] != '\0' &&
350 		    strcmp(dp->d_name, trail->tr_filename) <= 0) {
351 			continue;
352 		}
353 		PJDLOG_VERIFY(strlcpy(curfile, dp->d_name, sizeof(curfile)) <
354 		    sizeof(curfile));
355 	}
356 	if (curfile[0] == '\0') {
357 		/*
358 		 * There are no new trail files, so we return.
359 		 * We don't clear trail_filename string, to know where to
360 		 * start when new file appears.
361 		 */
362 		PJDLOG_ASSERT(trail->tr_filefd == -1);
363 		pjdlog_debug(1, "No new trail files.");
364 		return;
365 	}
366 	PJDLOG_VERIFY(strlcpy(trail->tr_filename, curfile,
367 	    sizeof(trail->tr_filename)) < sizeof(trail->tr_filename));
368 	dfd = dirfd(trail->tr_dirfp);
369 	PJDLOG_ASSERT(dfd >= 0);
370 	trail->tr_filefd = openat(dfd, trail->tr_filename, O_RDONLY);
371 	if (trail->tr_filefd == -1) {
372 		pjdlog_errno(LOG_ERR,
373 		    "Unable to open file \"%s/%s\", skipping",
374 		    trail->tr_dirname, trail->tr_filename);
375 		goto again;
376 	}
377 	pjdlog_debug(1, "Found next trail file: \"%s/%s\".", trail->tr_dirname,
378 	    trail->tr_filename);
379 }
380 
381 /*
382  * Close current trial file.
383  */
384 void
385 trail_close(struct trail *trail)
386 {
387 
388 	PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC);
389 	PJDLOG_ASSERT(trail->tr_filefd >= 0);
390 	PJDLOG_ASSERT(trail->tr_filename[0] != '\0');
391 
392 	PJDLOG_VERIFY(close(trail->tr_filefd) == 0);
393 	trail->tr_filefd = -1;
394 }
395 
396 /*
397  * Reset trail state. Used when connection is disconnected and we will
398  * need to start over after reconnect. Trail needs to be already closed.
399  */
400 void
401 trail_reset(struct trail *trail)
402 {
403 
404 	PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC);
405 	PJDLOG_ASSERT(trail->tr_filefd == -1);
406 
407 	trail->tr_filename[0] = '\0';
408 }
409 
410 /*
411  * Unlink current trial file.
412  */
413 void
414 trail_unlink(struct trail *trail, const char *filename)
415 {
416 	int dfd;
417 
418 	PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC);
419 	PJDLOG_ASSERT(filename != NULL);
420 	PJDLOG_ASSERT(filename[0] != '\0');
421 
422 	dfd = dirfd(trail->tr_dirfp);
423 	PJDLOG_ASSERT(dfd >= 0);
424 
425 	if (unlinkat(dfd, filename, 0) == -1) {
426 		pjdlog_errno(LOG_ERR, "Unable to remove \"%s/%s\"",
427 		    trail->tr_dirname, filename);
428 	} else {
429 		pjdlog_debug(1, "Trail file \"%s/%s\" removed.",
430 		    trail->tr_dirname, filename);
431 	}
432 }
433 
434 /*
435  * Return true if we should switch to next trail file.
436  * We don't switch if our file name ends with ".not_terminated" and it
437  * exists (ie. wasn't renamed).
438  */
439 bool
440 trail_switch(struct trail *trail)
441 {
442 	char filename[PATH_MAX];
443 	int fd;
444 
445 	PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC);
446 	PJDLOG_ASSERT(trail->tr_filefd >= 0);
447 
448 	if (!trail_is_not_terminated(trail->tr_filename))
449 		return (true);
450 	fd = dirfd(trail->tr_dirfp);
451 	PJDLOG_ASSERT(fd >= 0);
452 	if (faccessat(fd, trail->tr_filename, F_OK, 0) == 0)
453 		return (false);
454 	if (errno != ENOENT) {
455 		pjdlog_errno(LOG_ERR, "Unable to access file \"%s/%s\"",
456 		    trail->tr_dirname, trail->tr_filename);
457 	}
458 	strlcpy(filename, trail->tr_filename, sizeof(filename));
459 	if (!trail_find(trail)) {
460 		pjdlog_error("Trail file \"%s/%s\" disappeared.",
461 		    trail->tr_dirname, trail->tr_filename);
462 		return (true);
463 	}
464 	pjdlog_debug(1, "Trail file \"%s/%s\" was renamed to \"%s/%s\".",
465 	    trail->tr_dirname, filename, trail->tr_dirname,
466 	    trail->tr_filename);
467 	return (true);
468 }
469 
470 const char *
471 trail_filename(const struct trail *trail)
472 {
473 
474 	PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC);
475 
476 	return (trail->tr_filename);
477 }
478 
479 int
480 trail_filefd(const struct trail *trail)
481 {
482 
483 	PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC);
484 
485 	return (trail->tr_filefd);
486 }
487 
488 int
489 trail_dirfd(const struct trail *trail)
490 {
491 
492 	PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC);
493 
494 	return (dirfd(trail->tr_dirfp));
495 }
496 
497 /*
498  * Find the last file in the directory opened under dirfp.
499  */
500 void
501 trail_last(DIR *dirfp, char *filename, size_t filenamesize)
502 {
503 	char curfile[PATH_MAX];
504 	struct dirent *dp;
505 
506 	PJDLOG_ASSERT(dirfp != NULL);
507 
508 	curfile[0] = '\0';
509 
510 	rewinddir(dirfp);
511 	while ((dp = readdir(dirfp)) != NULL) {
512 		if (dp->d_name[0] < '0' || dp->d_name[0] > '9')
513 			continue;
514 		if (dp->d_type == DT_UNKNOWN)
515 			dp->d_type = trail_type(dirfp, dp->d_name);
516 		/* We are only interested in regular files, skip the rest. */
517 		if (dp->d_type != DT_REG)
518 			continue;
519 		/* Skip all files "greater" than curfile. */
520 		if (curfile[0] != '\0' && strcmp(dp->d_name, curfile) < 0)
521 			continue;
522 		PJDLOG_VERIFY(strlcpy(curfile, dp->d_name, sizeof(curfile)) <
523 		    sizeof(curfile));
524 	}
525 	if (curfile[0] == '\0') {
526 		/*
527 		 * There are no trail files, so we return.
528 		 */
529 		pjdlog_debug(1, "No trail files.");
530 		bzero(filename, filenamesize);
531 		return;
532 	}
533 	PJDLOG_VERIFY(strlcpy(filename, curfile, filenamesize) < filenamesize);
534 	pjdlog_debug(1, "Found the most recent trail file: \"%s\".", filename);
535 }
536 
537 /*
538  * Check if the given file name is a valid audit trail file name.
539  * Possible names:
540  * 20120106132657.20120106132805
541  * 20120106132657.not_terminated
542  * 20120106132657.crash_recovery
543  * If two names are given, check if the first name can be renamed
544  * to the second name. When renaming, first part of the name has
545  * to be identical and only the following renames are valid:
546  * 20120106132657.not_terminated -> 20120106132657.20120106132805
547  * 20120106132657.not_terminated -> 20120106132657.crash_recovery
548  */
549 bool
550 trail_validate_name(const char *srcname, const char *dstname)
551 {
552 	int i;
553 
554 	PJDLOG_ASSERT(srcname != NULL);
555 
556 	if (strlen(srcname) != 2 * HALF_LEN + 1)
557 		return (false);
558 	if (srcname[HALF_LEN] != '.')
559 		return (false);
560 	for (i = 0; i < HALF_LEN; i++) {
561 		if (srcname[i] < '0' || srcname[i] > '9')
562 			return (false);
563 	}
564 	for (i = HALF_LEN + 1; i < 2 * HALF_LEN - 1; i++) {
565 		if (srcname[i] < '0' || srcname[i] > '9')
566 			break;
567 	}
568 	if (i < 2 * HALF_LEN - 1 &&
569 	    strcmp(srcname + HALF_LEN + 1, "not_terminated") != 0 &&
570 	    strcmp(srcname + HALF_LEN + 1, "crash_recovery") != 0) {
571 		return (false);
572 	}
573 
574 	if (dstname == NULL)
575 		return (true);
576 
577 	/* We tolarate if both names are identical. */
578 	if (strcmp(srcname, dstname) == 0)
579 		return (true);
580 
581 	/* We can only rename not_terminated files. */
582 	if (strcmp(srcname + HALF_LEN + 1, "not_terminated") != 0)
583 		return (false);
584 	if (strlen(dstname) != 2 * HALF_LEN + 1)
585 		return (false);
586 	if (strncmp(srcname, dstname, HALF_LEN + 1) != 0)
587 		return (false);
588 	for (i = HALF_LEN + 1; i < 2 * HALF_LEN - 1; i++) {
589 		if (dstname[i] < '0' || dstname[i] > '9')
590 			break;
591 	}
592 	if (i < 2 * HALF_LEN - 1 &&
593 	    strcmp(dstname + HALF_LEN + 1, "crash_recovery") != 0) {
594 		return (false);
595 	}
596 
597 	return (true);
598 }
599 
600 int
601 trail_name_compare(const char *name0, const char *name1)
602 {
603 	int ret;
604 
605 	ret = strcmp(name0, name1);
606 	if (ret == 0)
607 		return (TRAIL_IDENTICAL);
608 	if (strncmp(name0, name1, HALF_LEN + 1) == 0)
609 		return (TRAIL_RENAMED);
610 	return (ret < 0 ? TRAIL_OLDER : TRAIL_NEWER);
611 }
612