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