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