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