trail.c revision 243734
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
67struct 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
81bool
82trail_is_not_terminated(const char *filename)
83{
84
85	return (strcmp(filename + HALF_LEN, ".not_terminated") == 0);
86}
87
88bool
89trail_is_crash_recovery(const char *filename)
90{
91
92	return (strcmp(filename + HALF_LEN, ".crash_recovery") == 0);
93}
94
95struct trail *
96trail_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
141void
142trail_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
156static uint8_t
157trail_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 */
179static bool
180trail_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 */
205void
206trail_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);
225again:
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 */
319void
320trail_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
329again:
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 */
384void
385trail_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 */
400void
401trail_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 */
413void
414trail_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 */
439bool
440trail_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
470const char *
471trail_filename(const struct trail *trail)
472{
473
474	PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC);
475
476	return (trail->tr_filename);
477}
478
479int
480trail_filefd(const struct trail *trail)
481{
482
483	PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC);
484
485	return (trail->tr_filefd);
486}
487
488int
489trail_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 */
500void
501trail_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 */
549bool
550trail_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
600int
601trail_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