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
65struct 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
79bool
80trail_is_not_terminated(const char *filename)
81{
82
83	return (strcmp(filename + HALF_LEN, ".not_terminated") == 0);
84}
85
86bool
87trail_is_crash_recovery(const char *filename)
88{
89
90	return (strcmp(filename + HALF_LEN, ".crash_recovery") == 0);
91}
92
93struct trail *
94trail_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
139void
140trail_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
154static uint8_t
155trail_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 */
177static bool
178trail_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 */
203void
204trail_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);
223again:
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 */
317void
318trail_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
327again:
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	PJDLOG_VERIFY(strlcpy(trail->tr_filename, curfile,
365	    sizeof(trail->tr_filename)) < sizeof(trail->tr_filename));
366	dfd = dirfd(trail->tr_dirfp);
367	PJDLOG_ASSERT(dfd >= 0);
368	trail->tr_filefd = openat(dfd, trail->tr_filename, O_RDONLY);
369	if (trail->tr_filefd == -1) {
370		pjdlog_errno(LOG_ERR,
371		    "Unable to open file \"%s/%s\", skipping",
372		    trail->tr_dirname, trail->tr_filename);
373		goto again;
374	}
375	pjdlog_debug(1, "Found next trail file: \"%s/%s\".", trail->tr_dirname,
376	    trail->tr_filename);
377}
378
379/*
380 * Close current trial file.
381 */
382void
383trail_close(struct trail *trail)
384{
385
386	PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC);
387	PJDLOG_ASSERT(trail->tr_filefd >= 0);
388	PJDLOG_ASSERT(trail->tr_filename[0] != '\0');
389
390	PJDLOG_VERIFY(close(trail->tr_filefd) == 0);
391	trail->tr_filefd = -1;
392}
393
394/*
395 * Reset trail state. Used when connection is disconnected and we will
396 * need to start over after reconnect. Trail needs to be already closed.
397 */
398void
399trail_reset(struct trail *trail)
400{
401
402	PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC);
403	PJDLOG_ASSERT(trail->tr_filefd == -1);
404
405	trail->tr_filename[0] = '\0';
406}
407
408/*
409 * Unlink current trial file.
410 */
411void
412trail_unlink(struct trail *trail, const char *filename)
413{
414	int dfd;
415
416	PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC);
417	PJDLOG_ASSERT(filename != NULL);
418	PJDLOG_ASSERT(filename[0] != '\0');
419
420	dfd = dirfd(trail->tr_dirfp);
421	PJDLOG_ASSERT(dfd >= 0);
422
423	if (unlinkat(dfd, filename, 0) == -1) {
424		pjdlog_errno(LOG_ERR, "Unable to remove \"%s/%s\"",
425		    trail->tr_dirname, filename);
426	} else {
427		pjdlog_debug(1, "Trail file \"%s/%s\" removed.",
428		    trail->tr_dirname, filename);
429	}
430}
431
432/*
433 * Return true if we should switch to next trail file.
434 * We don't switch if our file name ends with ".not_terminated" and it
435 * exists (ie. wasn't renamed).
436 */
437bool
438trail_switch(struct trail *trail)
439{
440	char filename[PATH_MAX];
441	int fd;
442
443	PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC);
444	PJDLOG_ASSERT(trail->tr_filefd >= 0);
445
446	if (!trail_is_not_terminated(trail->tr_filename))
447		return (true);
448	fd = dirfd(trail->tr_dirfp);
449	PJDLOG_ASSERT(fd >= 0);
450	if (faccessat(fd, trail->tr_filename, F_OK, 0) == 0)
451		return (false);
452	if (errno != ENOENT) {
453		pjdlog_errno(LOG_ERR, "Unable to access file \"%s/%s\"",
454		    trail->tr_dirname, trail->tr_filename);
455	}
456	strlcpy(filename, trail->tr_filename, sizeof(filename));
457	if (!trail_find(trail)) {
458		pjdlog_error("Trail file \"%s/%s\" disappeared.",
459		    trail->tr_dirname, trail->tr_filename);
460		return (true);
461	}
462	pjdlog_debug(1, "Trail file \"%s/%s\" was renamed to \"%s/%s\".",
463	    trail->tr_dirname, filename, trail->tr_dirname,
464	    trail->tr_filename);
465	return (true);
466}
467
468const char *
469trail_filename(const struct trail *trail)
470{
471
472	PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC);
473
474	return (trail->tr_filename);
475}
476
477int
478trail_filefd(const struct trail *trail)
479{
480
481	PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC);
482
483	return (trail->tr_filefd);
484}
485
486int
487trail_dirfd(const struct trail *trail)
488{
489
490	PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC);
491
492	return (dirfd(trail->tr_dirfp));
493}
494
495/*
496 * Find the last file in the directory opened under dirfp.
497 */
498void
499trail_last(DIR *dirfp, char *filename, size_t filenamesize)
500{
501	char curfile[PATH_MAX];
502	struct dirent *dp;
503
504	PJDLOG_ASSERT(dirfp != NULL);
505
506	curfile[0] = '\0';
507
508	rewinddir(dirfp);
509	while ((dp = readdir(dirfp)) != NULL) {
510		if (dp->d_name[0] < '0' || dp->d_name[0] > '9')
511			continue;
512		if (dp->d_type == DT_UNKNOWN)
513			dp->d_type = trail_type(dirfp, dp->d_name);
514		/* We are only interested in regular files, skip the rest. */
515		if (dp->d_type != DT_REG)
516			continue;
517		/* Skip all files "greater" than curfile. */
518		if (curfile[0] != '\0' && strcmp(dp->d_name, curfile) < 0)
519			continue;
520		PJDLOG_VERIFY(strlcpy(curfile, dp->d_name, sizeof(curfile)) <
521		    sizeof(curfile));
522	}
523	if (curfile[0] == '\0') {
524		/*
525		 * There are no trail files, so we return.
526		 */
527		pjdlog_debug(1, "No trail files.");
528		bzero(filename, filenamesize);
529		return;
530	}
531	PJDLOG_VERIFY(strlcpy(filename, curfile, filenamesize) < filenamesize);
532	pjdlog_debug(1, "Found the most recent trail file: \"%s\".", filename);
533}
534
535/*
536 * Check if the given file name is a valid audit trail file name.
537 * Possible names:
538 * 20120106132657.20120106132805
539 * 20120106132657.not_terminated
540 * 20120106132657.crash_recovery
541 * If two names are given, check if the first name can be renamed
542 * to the second name. When renaming, first part of the name has
543 * to be identical and only the following renames are valid:
544 * 20120106132657.not_terminated -> 20120106132657.20120106132805
545 * 20120106132657.not_terminated -> 20120106132657.crash_recovery
546 */
547bool
548trail_validate_name(const char *srcname, const char *dstname)
549{
550	int i;
551
552	PJDLOG_ASSERT(srcname != NULL);
553
554	if (strlen(srcname) != 2 * HALF_LEN + 1)
555		return (false);
556	if (srcname[HALF_LEN] != '.')
557		return (false);
558	for (i = 0; i < HALF_LEN; i++) {
559		if (srcname[i] < '0' || srcname[i] > '9')
560			return (false);
561	}
562	for (i = HALF_LEN + 1; i < 2 * HALF_LEN - 1; i++) {
563		if (srcname[i] < '0' || srcname[i] > '9')
564			break;
565	}
566	if (i < 2 * HALF_LEN - 1 &&
567	    strcmp(srcname + HALF_LEN + 1, "not_terminated") != 0 &&
568	    strcmp(srcname + HALF_LEN + 1, "crash_recovery") != 0) {
569		return (false);
570	}
571
572	if (dstname == NULL)
573		return (true);
574
575	/* We tolarate if both names are identical. */
576	if (strcmp(srcname, dstname) == 0)
577		return (true);
578
579	/* We can only rename not_terminated files. */
580	if (strcmp(srcname + HALF_LEN + 1, "not_terminated") != 0)
581		return (false);
582	if (strlen(dstname) != 2 * HALF_LEN + 1)
583		return (false);
584	if (strncmp(srcname, dstname, HALF_LEN + 1) != 0)
585		return (false);
586	for (i = HALF_LEN + 1; i < 2 * HALF_LEN - 1; i++) {
587		if (dstname[i] < '0' || dstname[i] > '9')
588			break;
589	}
590	if (i < 2 * HALF_LEN - 1 &&
591	    strcmp(dstname + HALF_LEN + 1, "crash_recovery") != 0) {
592		return (false);
593	}
594
595	return (true);
596}
597
598int
599trail_name_compare(const char *name0, const char *name1)
600{
601	int ret;
602
603	ret = strcmp(name0, name1);
604	if (ret == 0)
605		return (TRAIL_IDENTICAL);
606	if (strncmp(name0, name1, HALF_LEN + 1) == 0)
607		return (TRAIL_RENAMED);
608	return (ret < 0 ? TRAIL_OLDER : TRAIL_NEWER);
609}
610